ARM Lab 3: Functions and secrets

Introduction

You should now be fairly comfortable working with JSON templates and parameter files, leveraging the various sources of ARM template, and refactoring variables and parameters and resources to customise the files for your requirements.

In this lab we will look at:

  • ARM functions
  • referencing information
    • for the current deployment
    • for resources that already exist
  • handling secrets with securetext

These are all fairly common as you get more ambitious with your own templates or start using some of the more complex templates that are on the GitHub repository.

ARM Functions

But before we start moving through those areas, let’s take a deeper look at the wealth of functions that are available to the ARM templates. We’ll start to see more of them as we work through the various sections of the lab so it is worth spending some time to understand that range of capability.

The documentation for the ARM template functions is one of those areas that you will visit often, and so we come to our third important short URL:

aka.ms/armfunc

We’ll now step through some of the functions available to ARM templates, and how they can be used. This will not cover all of them, as the documentation for the templates is pretty good, so if you need to understand something trivial like how to trim a string with whitespace then dive in to that area and dig out the information.

The functions are split into seven groups:

Function Group Use Case
Array and Object used to manipulate or test JSON arrays and objects
Comparison which are a group of test operators used by the condition function
Deployment which covers those related to the deployment job, e.g. the parameters and variables functions
Logical which are a group used in logical expressions, such as if, and and or, or converting strings to booleans
Numerical the group providing integer and floating point arithmetic operators
Resource a very useful set for working with Azure Resource Manager constructs, such as info and IDs for the subscription, resource group, resources and providers, plus keys of resources, and references to a resources current state
Strings and the final set provides a large set of functions to manipulate and test strings

Browse through the sections to understand the breadth of functions available.

Another way of thinking about functions is to split them into those used to:

  • get
  • manipulate
  • test

You will see many of these functions used by some of the more complex templates that we will come across as we continue to work through the labs.

Getting information

We have already done some of this when working through the previous labs. Your resource sections already have parameter and variable functions to pull out information from other parts of the template at runtime. You can also use the deployment function to get information about the current and parent template, which is useful when dealing with nested deployments.

We have also used the resourceGroup() function. When not specifying a specific group as the argument then it will default to the group we are deploying to, and we can pull out the name, location, tags, etc. Setting or defaulting a resource’s location to "[resourceGroup().location]" might just be the look up function you use most regularly.

We also pulled out the subscription().subscriptionID, but you can also get the subscription().tenantID as well.

Many of the resource operations require a resource’s unique ID. This is frequently used with nested resources (or sub-resources), or when deriving the resourceId for an existing resource.

Remember in the theory section that a resourceId is in the following format: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}

When you call resourceId(), you can specify just the resourceType and resourceName and it will default to the current subscription and resourceGroup. Or you can find the resourceId for resources in different resource groups, or other subscriptions within the same tenancy. Look at the following code block for some networking variables in a template:

"variables": {
    "vnetID": "[resourceId(parameters('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
    "subnet1Ref": "[concat(variables('vnetID'),'/subnets/', parameters('subnet1Name'))]"
},

Note that the subnet is a child resource for the virtual network, which is why the concatenation makes sense. You can use Resource Explorer to browse the resources within your subscription, where you can see the resourceIds for your existing resources.

You will often see the resourceId used in the variables section to simplfy the resources section as much as possible.

You can also listKeys or list{Value} from those same resourceIds. This is very useful in the outputs sections. Check the Azure documentation for listKeys and list{Value} as there are a couple of Bash and PowerShell commands to find out what list actions are supported by each resource type. Try out the example for the storage account resource type. You will see listkeys, listAccountSas and listServiceSas. Taking the listkeys as an example, this then ties in with the REST API reference material.

We have no specific lab section for this, but you will see the resource functions littered throughout the templates.

Copying the 101-vm-simple-linux

If you ran through the previous lab then you should already have created a lab3 area containing the contents of the 101-vm-simple-linux from the Quickstart templates.

If not then copy out the azuredeploy.json and azuredeploy.parameters.json out into new files in a new lab3 folder.

Change the values in the parameters file to define your own admin username and password. Also pick a different dnsLabelPrefix as this needs to be globally unique as it forms part of an externall accessible FQDN.

Handling securetext passwords with Key Vault

OK, time to get a little self-reliant! You should now be comfortable accessing the various resources and documentation area and capable of being more self sufficient, so I will stop detailing all of the steps. This one will be for you to find your own way rather than be guided.

First we’ll create a key vault with a new secret called ubuntuDefaultPassword. After that you will configure your template and parameter files to use that secret in place of the cleartext password in the parameters file.

It is probably worth stating the problem we are solving before you get started.

All of the templates that include a password field, or any other property that contains sensitive data, should have a type of securetext rather than string. The key thing that this does is to make sure that someone going through the deployment logs in the portal is not able to view the password. So that part is not a concern as long as we use securetext as the type.

If we are using a script to prompt a user to enter a password as part of the deployment then again, that would be transient and there should be nothing to see if it is prompted for interactively in the right way, such as with read -s -p "Enter password: " password to ensure that the -s switch prevents characters being echoed to the terminal.

So the real issue is avoiding scripts and parameter files from containing cleartext password if the location and access permissions would allow them to be viewed. This is where the use of key vault secrets makes perfect sense.

Creating a Key Vault

OK, let’s begin by creating a lab3a folder and an empty azuredeploy.json and azuredeploy.parameters.json file.

We’ll also need a key vault if you haven’t got one. I have shown below how to make one manually to save time. Or you can use a couple of CLI or PowerShell commands. Try searching for the Azure documentation page that can help with that. Or if you wish you can take a look at the 201-key-vault-secret-create quickstart template and use that to automate the creation if you know that this is something you will be doing repeatedly.

Here are the manual instructions:

In the Azure portal:

  • Add a new resource, Key Vault
  • Give it a unique name
  • Create a new resource group: keyVaults
  • Create it in your preferred location
  • Stick with the Standard pricing tier and the default access policy and principal
  • Create

It should only take a few seconds to create. Once deployed:

  • Go into the Access policies area in the Setting area
    • Open up the advanced access policies
    • Enable access to Azure Resource Manager for template deployment
    • Save
  • Select Secrets in the Settings blade area
    • Add a secret
    • Manual upload
    • Name: ubuntuDefaultPassword
    • Value: Enter in a suitable password for the VM we’ll create
    • You don’t need to set an activation or expiry date
    • Create

As you have access (as the principal under the access policy), you will be able to go back into the portal to view or change the secret in the future. Or you can check the various CLI and PowerShell commands on the Azure Docs. For instance, I could retrieve the password using: az keyvault secret show --name ubuntuDefaultPassword --vault-name richeneyKeyVault --query value --output tsv

Reconfigure your parameters file

OK, time to roll up your sleeves. I’ll give you a few pointers and then a few requirements and leave you to your own devices for a while.

Pointers:

  • With the current (Dec 2017) functionality and schema format you cannot use dynamic key vault names unless you use nested templates
  • Search for the information online on how to configure static keyVault names when passing in secrets
  • Use commands or the resource explorer to find the id for your key vault
  • Your configuration in this lab for keyvaults and secrets should only affect the parameters file, not the main template

Requirements:

  1. Make sure the adminPassword is set with a reference to the secret from the keyVault rather than a cleartext value
  2. Move the vmName variable up into the parameters section and default it to the current value
  3. Update the list of allowed Ubuntu versions in your main template to the current 14.x and 16.x LTS versions available on the platform and the default to the 16.x version. (And feel free to add a few newer ones.)
  4. Update the parameters file as well:
    • Set the adminUsername to your username
    • Set the dnsLabelPrefix to something that will be unique for the FQDN

If you have time then work out the commands in either PowerShell or Bash to see the available Ubuntu virtual machine images from Canonical. (Hint: the publisher is Canonical.)

We will return to key vaults and secrets when we look at the nesting.

- Deploy into lab3

OK, if you haven’t done so already then create a lab3 resource group and deploy the new template into that in order to test it. Connect to the VM and confirm that the password is the one you stored in the Key Vault.

Before we configure the condition we will first spend a little time cleaning up a few more things in our template to make it more useful. Once that is done and tested then we’ll add in a condition to make the public IP (PIP) and associated DNS name optional.

This will be a little painful, but once it is done then the template will be far more useful as a building block.

If you look at the resources that have been spun up in resource group lab3 from our lab3 template then you’ll notice a few things:

lab3 resources

  • The vNet (and subnet) are fixed variables for both the name and the address space, so additional VMs deployed to this resource group using this template would share the same networking. This is fine, but let’s make them parameters with defaults so that they can be overriden if need be.
  • This template creates first level Managed Disks for the OS (30GB) and the data disk (1023GB), with unique names prefixed with the vmName.
  • The PIP and NIC are fixed names and would conflict if a second was added

Refactoring the networking parameters and variables

OK, we’ve addressed the password. Let’s start turning the template itself into a more useful building block by refactoring a few things.

  1. Create parameters for the following:
    • vnetName with no default
    • vnetPrefix, defaulting to “10.0.0.0/16”
    • subnetName, defaulting to “Subnet”
    • subnetPrefix, defaulting to “10.0.0.0/24”
  2. Remove the corresponding variables
    • vnetName is replacing virtualNetworkName
    • vnetPrefix is replacing addressPrefix
  3. Remove the default for the vmName
  4. Change the value for the nicName variable to be the vmName with ‘-nic’ at the end
  5. Change the publicIPAddressName variable to pipName
    • Change the value for the pipName variable to be the vmName with ‘-pip’ at the end
  6. Change the publicIPAddressType variable to pipType
  7. Change the value for the vmSize variable to “Standard_B1s”

In the parameters file, add in new virtualNetworkName and subnetName parameters and remove both vmName and dnsLabelPrefix. Here is an example:

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "value": "richeney"
    },
    "adminPassword": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/2ca40be1-7680-4f2b-92f7-06b2123a68cc/resourceGroups/keyVaults/providers/Microsoft.KeyVault/vaults/richeneyKeyVault"
        },
        "secretName": "ubuntuDefaultPassword"
      }
    },
    "virtualNetworkName": {
        "value": "ubuntuVnet"
    },
    "subnetName": {
        "value": "ubuntuSubnet"
    }
  }
}

Submitting using both parameter files and inline parameters

Once you have done that then we’ll deploy the template twice into the lab3 resource group.

Note that both deployments below use the --parameters switch twice, first to pull in the parameters file, and then with some inline name=value pairs to to specify the VM name and DNS label prefix.

rg=lab3

dir=$(pwd)
template=$dir/azuredeploy.json
parms=$dir/azuredeploy.parameters.json

az group create --name $rg --location westeurope

job=job.$(date --utc +"%Y%m%d.%H%M%S")
az group deployment create --parameters "@$parms" --parameters vmName=lab3UbuntuVm2 dnsLabelPrefix=richeneylab3vm2 --template-file $template --resource-group $rg --name $job --no-wait

job=job.$(date --utc +"%Y%m%d.%H%M%S")
az group deployment create --parameters "@$parms" --parameters vmName=lab3UbuntuVm3 dnsLabelPrefix=richeneylab3vm3 --template-file $template --resource-group $rg --name $job --no-wait

The deployment lines also have the --no-wait switch at the end so that the deployments go in faster.

Combining parameter files and inline parameters in this way can be very useful, particularly in multi-tenancy environments.

Final lab3 template and parameter files

Lab 3 Files

If yours aren’t quite right then use the compare tool from the Command Palette.

What’s next

In the next section we will look at using the copy property to create multiple of a resource, or of a property (such as managed disks) within a resource.

◄ Lab 2: Sources ▲ Index Lab 4: Conditions ►