2017-11-26

OpenBSD on Azure, Take 2

Reyk Flöter has updated the OpenBSD-on-Azure methods I've previously outlined. In lieu of having to sully your OpenBSD install with OpenSSL or — worse — Python, he's written a replacement for WALinuxAgent that is leaner and meaner and better suited for the platform.

Consider this an alternate approach to the original instructions. They will continue to work, but this one will, too. To keep things interesting, we're going to use a different set of Azure management tools. Instead of Azure CLI/Cloud Shell, we'll use Azure's PowerShell module. This isn't strictly required. Use the tools you like best.

Create your OpenBSD VM as desired. One way to do this is with the PowerShell Hyper-V cmdlets.

Define your system particulars. Do this from an elevated PowerShell prompt:

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 2
$openbsd_iso_path = Get-Item -Path 'C:\path\to\OpenBSD\6.2\amd64\install62.iso'
$vm_vhd_path      = (Join-Path ${Env:PUBLIC} 'Documents\Hyper-V\Virtual hard disks\openbsd62.vhd')
$vm_name          = 'myopenbsdvm'
$vm_switch_name   = Get-VMSwitch | Select-Object -First 1 -ExpandProperty Name

Create a new VHD and VM:

$vhd_properties = @{
  'Path'      = $vm_vhd_path;
  'SizeBytes' = 4 * [Math]::Pow(2,30); # OpenBSD ain't as svelte as it used to be
  'Fixed'     = $True;
}
New-VHD @vhd_properties

$vm_properties = @{
  'Name'               = $vm_name;
  'MemoryStartupBytes' = 512 * [Math]::Pow(2,20);
  'Generation'         = 1;
  'VHDPath'            = $vhd_properties.Path;
}
New-VM @vm_properties

Remove the default unconfigured virtual network adapter and virtual DVD drive and add your own:

$vm_net_adapter = @{
  'VMName'     = $vm_properties.Name;
  'SwitchName' = $vm_switch_name;
}
Remove-VMNetworkAdapter -VMName $vm_properties.Name
Add-VMNetworkAdapter @vm_net_adapter

$vm_dvd_properties = @{
  'VMName'             = $vm_properties.Name
  'ControllerNumber'   = 1;
  'ControllerLocation' = 0;
  'Confirm'            = $False;
}
Remove-VMDvdDrive @vm_dvd_properties

$vm_dvd_properties['Path'] = $openbsd_iso_path.FullName

Add-VMDvdDrive @vm_dvd_properties

When the OpenBSD VM is configured start it to begin the OpenBSD installation process:

Start-VM -VMName $vm_properties.Name

Connect to the VM and install OpenBSD as you normally would. Reboot. You've patched this VM, right? Right?

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

syspatch
reboot

When the machine is patched, add Reyk's new cloud agent:

pkg_add -I git
git clone https://github.com/reyk/cloud-agent.git
cd ./cloud-agent
make
install -m0755 ./agent/cloud-agent /usr/local/libexec
install -m0755 ./cms/bin/cms       /usr/local/bin

echo dhcp > ./hostname.hvn0.tmp
echo !/usr/local/libexec/cloud-agent "\$if" >> ./hostname.hvn0.tmp
mv ./hostname.hvn0.tmp /etc/hostname.hvn0

touch /etc/boot.conf
cp -p /etc/boot.conf /etc/boot.conf.tmp
echo stty com0 115200 >> /etc/boot.conf.tmp
echo set tty com0 >> /etc/boot.conf.tmp
mv /etc/boot.conf.tmp /etc/boot.conf

sync
halt -p

You can upload this image to Azure with AzCopy if you already have a storage account. For variety, let's assume you need to set this up from scratch again and this time, let's do it from a Windows box with PowerShell. Do this from the Hyper-V host, or from a dedicated admin box. VMs are good for this sort of thing.

In an effort to promote compatibility with their browser(s), Microsoft has kindly made a number of Windows VMs available for free download. They are intended for short-term testing scenarios and are allegedly time-limited to 90 days, but I've never kept one around long enough to find out. You can download a free, disposable Windows VM that works on VirtualBox, Vagrant, Hyper-V, VMware, or Parallels, so most of your name-brand virtualization platforms are covered.

From your Windows box, install a modern version of the .NET Framework and Azure PowerShell and get started.

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 2
Login-AzureRmAccount
Get-AzureRmSubscription -OutVariable my_subs
# find your desired Azure subscription or filter it out of $my_subs
Select-AzureRmSubscription -SubscriptionID <mySubscriptionID>

# create image library group
$group_properties = @{
  'Location' = 'westus2';
  'Name'     = 'mylibrary';
}
New-AzureRmResourceGroup @group_properties

# create storage account
$storage_account_properties = @{
  'ResourceGroupName' = $group_properties.Name;
  'Name'              = 'myopenbsdimagelibrary';
  'Location'          = $group_properties.Location;
  'Sku'               = 'Standard_LRS';
}
$my_storage_account = New-AzureRmStorageAccount @storage_account_properties

# and container
$container_properties = @{
  'Context' = $my_storage_account.Context;
  'Name'    = 'vhds';
}
New-AzureStorageContainer @container_properties

Get the access key for the storage account so you can write to it:

$key_properties = @{
  'ResourceGroupName' = $group_properties.Name;
  'Name'              = $my_storage_account.StorageAccountName;
}
(Get-AzureRmStorageAccountKey @key_properties).Value[0]

Use this key to upload your OpenBSD VM .VHD file. If you went with the Windows VM option, you need to export this string to your Hyper-V host where the .VHD file resides. Directly connecting to the VM through vmconnect to pass strings between host and VM may not work (I've seen it work seamlessly and I've seen it not), but remoting into it with RDP will let you share clipboards. Failing that, you can write the string to disk and do some Mount-VHD magic to transfer it. You can save it to disk as a SecureString with a specific key. You can GPG encrypt it as per Dear Microsoft, This is How You Encrypt a File. You can use the under-documented file encryption feature of Password Safe. The pwsafe.exe binary can be downloaded from GitHub and run from a command line without the need to install anything, which can be very useful in situations like this. You could put your OpenBSD .VHD file inside a bigger .VHD and mount it in the Windows VM and enjoy a complex matryoshkaesque kind of virtualized Winception. You can log into portal.azure.com and get it from the UI. You can write AES or Salsa20 in PowerShell and pass it between machines that way.

What I'm saying is that you have options.

If you're paranoid, you can worry less about the security of the storage account key between your VM and your host and simply rotate the key as soon as you're done. Upload the .VHD with AzCopy, then rollover the access key:

$key_properties['KeyName'] = 'key1'
New-AzureRmStorageAccountKey @key_properties

You can even do this before and after you upload.

Upload your OpenBSD image .VHD file to your storage account from the Hyper-V host with AzCopy. One example of how to do this is borrowed from my first howto:

$sec_str       = ConvertTo-SecureString -AsPlainText -Force -String 'STORAGE_ACCOUNT_KEY_GOES_HERE=='
$key_file_path = 'C:\Path\to\keyfile.xml'
Export-CliXml -InputObject $sec_str -Force -Encoding 'UTF8' -Path $key_file_path

$azcopy_properties = @{
  'AzCopyBinPath' = (Join-Path ${Env:ProgramFiles(x86)} 'Microsoft SDKs\Azure\AzCopy\AzCopy.exe');
  'BlobType'      = 'Page';
  'Destination'   = 'https://myopenbsdimagelibrary.blob.core.windows.net/vhds';
  'KeyFile'       = $key_file_path;
  'Path'          = [System.IO.Path]::GetDirectoryName($vm_vhd_path);
  'Pattern'       = [System.IO.Path]::GetFileName($vm_vhd_path);
}
.\azcopy_upload.ps1 @azcopy_properties

From the admin VM, create an image config and attach the .VHD file to it:

$image_config = New-AzureRmImageConfig -Location $group_properties.Location
$vhd_path     = '/openbsd62.vhd'
$disk_properties = @{
  'BlobUri' = $my_storage_account.Context.BlobEndPoint + $container_properties.Name + $vhd_path;
  'Caching' = 'ReadWrite';
  'Image'   = $image_config;
  'OsType'  = 'Linux'; # this was the most unkindest cut of all
}
Set-AzureRmImageOsDisk @disk_properties

$image_properties = @{
  'ResourceGroupName' = $group_properties.Name;
  'Image'             = $image_config;
  'ImageName'         = 'myopenbsd62image';
}
$my_image = New-AzureRmImage @image_properties

Make a new resource group and set up a new VM with your new OpenBSD image:

$bsd_vm_group_properties = @{
  'Location' = 'westus2';
  'Name'     = 'myopenbsdvms';
}
New-AzureRmResourceGroup @bsd_vm_group_properties

Create your virtual networking config. As is the case with all networking setups, this is the worst part. You could go nuts getting into the details here. For this example I'm eyeballing a small RFC-1918 subnet with one public IP address for one OpenBSD VM:

$bsd_vnet_properties = @{
  'ResourceGroupName' = $bsd_vm_group_properties.Name;
  'Location'          = $bsd_vm_group_properties.Location;
  'AddressPrefix'     = '10.0.2.0/24'; # adjust this accordingly
  'Name'              = 'VNET1';
}
$bsd_vnet = New-AzureRmVirtualNetwork @bsd_vnet_properties

$bsd_subnet_properties = @{
  'AddressPrefix' = '10.0.2.0/24';
  'Name'          = 'Subnet-1';
}
$bsd_vnet = $bsd_vnet | Add-AzureRmVirtualNetworkSubnetConfig @bsd_subnet_properties | Set-AzureRmVirtualNetwork

$bsd_public_ip_properties = @{
  'ResourceGroupName' = $bsd_vm_group_properties.Name;
  'Location'          = $bsd_vm_group_properties.Location;
  'AllocationMethod'  = 'Dynamic';
  'Name'              = 'mypublicip';
}
$bsd_public_ip = New-AzureRmPublicIpAddress @bsd_public_ip_properties

Create a virtual NIC for your VM. This is actually kind of brilliant on Azure's behalf, as a virtual NIC can be configured independently of the VM to which it's attached. In other words, you can stop and delete the VM, make a new one, attach it to the same NIC, and use the same network settings on the new VM. Hallelujah.

$bsd_vnic_properties = @{
  'ResourceGroupName' = $bsd_vm_group_properties.Name;
  'Location'          = $bsd_vm_group_properties.Location;
  'Name'              = 'myvnic1';
  'PublicIpAddressId' = $bsd_public_ip.Id;
  'SubnetId'          = $bsd_vnet.Subnets[0].Id; # Be careful here if you have multiple subnets
}
$bsd_vnic = New-AzureRmNetworkInterface @bsd_vnic_properties

Set up the VM config:

$bsd_vm_config_properties = @{
  'VMName' = 'openbsdvm1';
  'VMSize' = 'Basic_A1';
}
$vm_config = New-AzureRmVMConfig @bsd_vm_config_properties

Set a username and a password:

$my_credential = Get-Credential -UserName 'azureuser' -Message 'OpenBSD user credential' # This is interactive, for the password

If you really want to do this programmatically, set $my_credential like so:

$password_secure_string = ConvertTo-SecureString -Force -AsPlainText -String 'myPassw0rdG03sHere'
$my_credential          = New-Object System.Management.Automation.PSCredential 'azureuser',$password_secure_string

Attach the virtual NIC:

$vm_config = Add-AzureRmVMNetworkInterface -VM $vm_config -Id $bsd_vnic.Id

Finish setting up the VM and create it from the custom image you uploaded:

$vm_os_properties = @{
  'ComputerName' = $bsd_vm_config_properties.VMName;
  'Credential'   = $my_credential;
  'Linux'        = $True;
  'VM'           = $vm_config;
}
$vm_object = Set-AzureRmVMOperatingSystem @vm_os_properties

$vm_src_image_properties = @{
  'Id' = $my_image.Id;
  'VM' = $vm_config;
}
Set-AzureRmVMSourceImage @vm_src_image_properties

$vm_properties = @{
  'ResourceGroupName' = $bsd_vm_group_properties.Name;
  'Location'          = $bsd_vm_group_properties.Location;
  'Verbose'           = $True;
  'VM'                = $vm_config;
}
New-AzureRmVM @vm_properties

When the VM provisions, check your work:

$my_vm = Get-AzureRmVm -ResourceGroupName $bsd_vm_group_properties.Name
$my_ip = Get-AzureRmPublicIpAddress -ResourceGroupName $bsd_vm_group_properties.Name -Name $bsd_public_ip.Name

SSH into the VM at its public IP, which you'll find in $my_ip.IpAddress. Use the username and password you set up in $my_credential. At some point there will be a Microsoft-blessed version of OpenSSH but for now use PuTTY or another BSD box:

ssh -2 -l azureuser ip.ad.dr.ess

This tutorial was really just meant to cover Reyk's excellent OpenBSD Azure cloud-agent software. It skips a lot of important implementation details. If you intend to use an OpenBSD host in Azure, ensure you've thought about and sufficiently addressed your network security group needs and any availability sets you may want to use to ensure service uptime across platform updates.

I wouldn't even use PowerShell in this manner anymore aside from a demonstration such as this one to prove it can be done. AzureRM templates are incredibly useful, so if you create a VM in this way you can check the "Automation script" tab of the VM in the Azure portal to look at how you can use a .JSON file and New-AzureRmResourceGroupDeployment to automate your VM creation process and all its assorted dependencies. More on this later.

External References and Resources

https://docs.microsoft.com/en-us/azure/virtual-machines/windows/tutorial-custom-images

https://azure.microsoft.com/en-us/blog/vm-image-blog-post/

https://docs.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell#create-virtual-machine

http://www.codeisahighway.com/how-to-create-a-virtual-machine-from-a-custom-image-using-arm-and-azure-powershell-v1-0-x/

No comments: