How to create a golden image of Ubuntu Pro 20.04 FIPS with Azure Image Builder

Key Value
Summary Create a golden image of Ubuntu Pro 20.04 FIPS with Azure Image Builder
Categories azure, cloud, FIPS
Difficulty 3


Duration: 2:00

In this tutorial, we will use Azure Image Builder to create a Ubuntu Pro 20.04 FIPS “golden” image in an Azure Shared Image Gallery.

Ubuntu 20.04 FIPS includes cryptographic validated modules that enable organisations to run and develop applications for the public sector, including regulated industries such as healthcare and finance.

We will be using a sample .json template to configure the image. To distribute the image to a Shared Image Gallery.

ⓘ Note: We are using a pre-enabled FIPS image, but you can also use the standard Ubuntu Pro if it better suits your needs.

What you’ll learn

  • How to set up an Azure environment with a Shared Image Gallery and the necessary resources to distribute an image within your environment.
  • How to create an image definition for Ubuntu Pro 20.04 FIPS, customizing the image build JSON for adding any other applications you may want in every Ubuntu VM.
  • How to create an image version in Azure Image Builder service.
  • How to create a VM from the image in the Shared Image Gallery.

What you’ll need


This tutorial is based off this previous one for Ubuntu Pro 18.04 with CIS hardening.

Setup your Shared Image Gallery

Duration: 5:00

We will be using some pieces of information repeatedly, so it makes sense to create some variables to store that information. This will make the instructions more efficient and easier to read.
***Then we will create the identity, image definition and gallery that we will need for the image.

Set variables for use throughout the tutorial

We will create a new resource group for this tutorial. The name should be one you are not already using. We will delete the resource group at the end of the tutorial.
Please note that in order to create a custom image, the VM Image Builder must be in the same resource group as the source-managed image.

# Resource group name - we are using ibUbuntuFIPSGalleryRG in this example
# Datacenter location - we are using West US 2 in this example
# Additional region to replicate the image to - we are using East US in this example

Now we will set variables for the Gallery Name and Image Definition Name. The image will be displayed in the Azure Portal as sigName/imageDefName.

# Name of the Azure Compute Gallery - myGallery in this example
# Name of the image definition to be created - myImageDef in this example
# image distribution metadata reference name

Create a variable for your subscription ID:
subscriptionID=$(az account show --query id --output tsv)

Now we are going to set variables for the Ubuntu Pro plan we are going to use in this tutorial. If you have an Ubuntu Pro private offer with Canonical that includes 24x7 Technical Support with SLAs, you will have a custom Offer and Sku. If not, we will use the plan name and product for the public Ubuntu Pro 20.04 FIPS from the Azure Marketplace, please note that we are using the gen2 Sku.

# ProPlanPublisher the 'Publisher' field for the Marketplace VM Offer we want to start from
# ProPlanOffer the 'Offer' field for the Marketplace VM Offer we want to start from
# ProPlanSku the 'Sku' field for the Marketplace VM Offer we want to start from

Create required resources, identities and permissions

Create the resource group:

az group create -n $sigResourceGroup -l $location --subscription $subscriptionID

Image Builder will use the user-identity provided to inject the image into the Azure Shared Image Gallery (SIG). In this example, you will create an Azure role definition that has the actions needed to distribute the image to the SIG. The role definition will then be assigned to the user-identity.

# create user assigned identity for image builder to access the storage account where the script is located
identityName=aibBuiUserId$(date +'%s')
az identity create -g $sigResourceGroup -n $identityName --subscription $subscriptionID

# get identity id
imgBuilderCliId=$(az identity show -g $sigResourceGroup -n $identityName --subscription $subscriptionID -o json | grep "clientId" | cut -c16- | tr -d '",')

# get the user identity URI, needed for the template

# this command will download an Azure role definition template, and update the template with the parameters specified earlier.
curl -o aibRoleImageCreation.json

imageRoleDefName="Azure Image Builder Image Def"$(date +'%s')

# update the definition
sed -i -e "s/<subscriptionID>/$subscriptionID/g" aibRoleImageCreation.json
sed -i -e "s/<rgName>/$sigResourceGroup/g" aibRoleImageCreation.json
sed -i -e "s/Azure Image Builder Service Image Creation Role/$imageRoleDefName/g" aibRoleImageCreation.json

# create role definitions
az role definition create --role-definition ./aibRoleImageCreation.json

# grant role definition to the user assigned identity
# If this gives an error, wait a bit longer and try again
az role assignment create \
    --assignee $imgBuilderCliId \
    --role "$imageRoleDefName" \
    --scope /subscriptions/$subscriptionID/resourceGroups/$sigResourceGroup

Create an image definition and gallery

In order to use VM Image Builder with Azure Compute Gallery, you will need to have an existing gallery and image definition. VM Image Builder does not create the gallery and image definition for you.

First, create a gallery:

az sig create \
    -g $sigResourceGroup \
    --gallery-name $sigName \
    --subscription $subscriptionID

Then, create an image definition, note the “hyper-v-generation” flag, this needs to be the same gen as the base image you are using.

az sig image-definition create \
   -g $sigResourceGroup \
   --gallery-name $sigName \
   --gallery-image-definition $imageDefName \
   --publisher $ProPlanPublisher \
   --offer $ProPlanOffer \
   --sku $ProPlanSku \
   --os-type Linux \
   --plan-name $ProPlanSku \
   --plan-product $ProPlanOffer \
   --plan-publisher $ProPlanPublisher \
   --hyper-v-generation V2 \
   --subscription $subscriptionID

Customise a template for our deployment

Duration: 2:00

We are now going to create a template that contains the build instructions for the “golden” image we want to create.

We can download a template for this from here.

curl -o UbuntuProFips2004SIGTemplate.json

We can then customise it to use the values we have set above. The sed commands replace the <variable> placeholders in UbuntuProFips2004SIGTemplate.json with the values for the parameters that we set earlier:

sed -i -e "s/<subscriptionID>/$subscriptionID/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<rgName>/$sigResourceGroup/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<imageDefName>/$imageDefName/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<sharedImageGalName>/$sigName/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<region1>/$location/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<region2>/$additionalregion/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<runOutputName>/$runOutputName/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s%<imgBuilderId>%$imgBuilderId%g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<ProPlanPublisher>/$ProPlanPublisher/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<ProPlanOffer>/$ProPlanOffer/g" UbuntuProFips2004SIGTemplate.json
sed -i -e "s/<ProPlanSku>/$ProPlanSku/g" UbuntuProFips2004SIGTemplate.json

Review the contents of the template file

Duration: 2:00

It is worth reviewing the entire (short) file, but we will focus on particular sections in more detail. We likely won’t need to change any of these values.

Note the section below:

        "source": {
            "type": "PlatformImage",
                "publisher": "canonical",
                "offer": "0001-com-ubuntu-pro-focal-fips",
                "sku": "pro-fips-20_04-gen2",
                "version": "latest",
        "planInfo": {
                    "planName": "pro-fips-20_04-gen2",
                    "planProduct": "0001-com-ubuntu-pro-focal-fips",
                    "planPublisher": "canonical"

This will show the plan details for the VM image you are using as a starting point for your golden image, whether it be from the Marketplace or a Private Offer.

The ‘customize’ section allows us to run commands as part of the image building process. The following command waits until Ubuntu’s ua client has attached to its subscription

        "customize": [
            "type": "Shell",
            "name": "WaitForUAtokenAutoAttach",
            "inline": [
                "sudo ua status --wait"

Next, you can add your own actions, like hardening the image or installing specific software.

            "type": "Shell",
            "name": "Placeholder for custom commands required in each Ubuntu VM",
            "inline": [
                "echo 'Replace me!'"

The following commands deregister the golden image from Ubuntu Pro and remove the machine-id. This will ensure that VMs generated from the golden image will generate their own unique IDs.

            "type": "Shell",
            "name": "DetachUA -- images created from this will auto attach themselves with new credentials",
            "inline": [
                "sudo ua detach --assume-yes && sudo rm -rf /var/log/ubuntu-advantage.log"

            "type": "Shell",
            "name": "Replace /etc/machine-id with empty file to ensure UA client does not see clones as duplicates",
            "inline": [
                "sudo rm -f /etc/machine-id && sudo touch /etc/machine-id"

Build/Create the image version

Duration: 30:00

We will now create the image version in the gallery.

First, we submit our image configuration to the Azure Image Builder service

az resource create \
    --resource-group $sigResourceGroup \
    --subscription $subscriptionID \
    --properties @UbuntuProFips2004SIGTemplate.json \
    --is-full-object \
    --resource-type Microsoft.VirtualMachineImages/imageTemplates \
    -n UbuntuProFips2004SIG01

Before the subscription can be used, you need to accept the legal terms of the image.

az vm image terms accept --plan $ProPlanSku --offer $ProPlanOffer --publisher $ProPlanPublisher --subscription $subscriptionID

In the next step we will start the image build. This step can take some time (25 mins on my testing), as Azure will actually launch a VM and run the steps we have defined. We need to wait for this to complete before we can create a VM.

az resource invoke-action \
     --resource-group $sigResourceGroup \
     --subscription $subscriptionID \
     --resource-type  Microsoft.VirtualMachineImages/imageTemplates \
     -n UbuntuProFips2004SIG01 \
     --action Run

While we are waiting for the command to run we can see the logs of the AIB build process by going to the storage account inside the resource group created by AIB . (ie. Azure Portal > Resource groups > [IT_ibUbuntuProGalleryRG_*** > Random ID of the storage account > Containers > packerlogs > Random ID of the container > customization.log > Download.

Completing the build process

Once it has completed, it will change from “Running” to show something like:

  "endTime": "2022-09-10T23:13:25.9008064Z",
  "name": "37962BEF-34DC-45B1-A1C6-E827CE20F89B",
  "startTime": "2022-09-10T22:48:19.7520483Z",
  "status": "Succeeded"

Create a VM from the shared image gallery from the Portal

Duration: 5:00

Create a VM from the Azure Portal

We will now create a VM from the Azure Portal.

Once you log in, click “Virtual machines”:

Click “Create” > “Virtual machine”

In the “Create a virtual machine” screen, you will need to click “See all images” below the “Image” drop down:

Select “Shared Images” on the left-hand side:

You will see the image created:
Screenshot from 2022-09-10 19-10-42

You should click this and it will select that as the image type.

Complete the remaining fields as you wish and click “Review + Create” to create a VM

Create and test a VM from the shared image gallery from the AZ CLI

Duration: 5:00

Create a VM from the command line

We are now going to create a VM from the Shared Image Gallery. As we created the image with plan information, we also need to specify that information when launching the instance.

If you already have an SSH key that you want to use, then use the following to launch the VM:

SSHPublicKeyPath=<path to your>
az vm create \
  --resource-group $sigResourceGroup \
  --subscription $subscriptionID \
  --name myAibGalleryVM \
  --admin-username aibuser \
  --location $location \
  --image "/subscriptions/$subscriptionID/resourceGroups/$sigResourceGroup/providers/Microsoft.Compute/galleries/$sigName/images/$imageDefName/versions/latest" \
  --ssh-key-values $SSHPublicKeyPath \
  --plan-name $ProPlanSku \
  --plan-product $ProPlanOffer \
  --public-ip-sku Standard \
  --plan-publisher $ProPlanPublisher

Alternatively, if you do not yet have SSH keys, you can use the following and replace the --ssh-key-values $SSHPublicKeyPath with --generate-ssh-keys (note that this may overwrite the ssh keypair “id_rsa” and “” under .ssh in your home directory):

az vm create \
  --resource-group $sigResourceGroup \
  --subscription $subscriptionID \
  --name myAibGalleryVM \
  --admin-username aibuser \
  --location $location \
  --image "/subscriptions/$subscriptionID/resourceGroups/$sigResourceGroup/providers/Microsoft.Compute/galleries/$sigName/images/$imageDefName/versions/latest" \
  --generate-ssh-keys \
  --plan-name $ProPlanSku \
  --plan-product $ProPlanOffer \
  --public-ip-sku Standard \
  --plan-publisher $ProPlanPublisher

When this completes, you should see something like the following:

  "fqdns": "",
  "id": "/subscriptions/50a71625-6dba-43a2-87ad-9eb26e52c9c4/resourceGroups/ibUbuntuFIPSGalleryRG/providers/Microsoft.Compute/virtualMachines/myAibGalleryVM",
  "identity": {
    "principalId": "632b1fc9-9d93-46da-bbd1-3b32e85f96eb",
    "tenantId": "40a524d9-f848-46d4-a96f-be6df491fe15",
    "type": "SystemAssigned",
    "userAssignedIdentities": null
  "location": "westus2",
  "macAddress": "00-0D-3A-F5-29-B8",
  "powerState": "VM running",
  "privateIpAddress": "",
  "publicIpAddress": "51.143.126.x",
  "resourceGroup": "ibUbuntuFIPSGalleryRG",
  "zones": ""

Note the publicIpAddress, which in my case is 51.143.126.x. This is what you can use to ssh into the machine in the next step.

You can ssh using that IP address and with the command

$ sudo ua status --wait

We can see that this VM is attached to an Ubuntu Pro subscription and is running a FIPS kernel.


Duration: 2:00

You should be able to see the Resource Groups that have been created as part of this tutorial by typing:

az group list --query [].name --output table --subscription $subscriptionID | grep $sigResourceGroup

In my case this returns:


Please confirm that you would like these to be deleted. If so, type:

az group delete --name [the name from above] --subscription $subscriptionID

for each of these Resource Groups

(You may find that deleting the first automatically deletes the second.)

That’s all folks!

Duration: 1:00

Great job! You’ve just created a Shared Image Gallery with an Ubuntu Pro 20.04 FIPS image inside, and launched and tested a VM created from this.

1 Like