John Downs

Building Human-Focused Software

Creating Azure Storage SAS Tokens with ARM Templates

This post was originally published on the Kloud blog.

Shared access signatures, sometimes also called SAS tokens, allow for delegating access to a designated part of an Azure resource with a defined set of permissions. They can be used to allow various types of access to your Azure services while keeping your access keys secret.

In a recent update to Azure Resource Manager, Microsoft has added the ability to create SAS tokens from ARM templates. While this is a general-purpose feature that will hopefully work across a multitude of Azure services, for now it only seems to work with Azure Storage (at least of the services I've checked). In this post I'll explain why this is useful, and give some example ARM templates that illustrate creating both account and service SAS tokens.

Use Cases

There are a few situations where it's helpful to be able to create SAS tokens for an Azure Storage account from an ARM template. One example is when using the Run From Package feature - an ARM template can deploy an App Service, deploy a storage account, and create a SAS token for a package blob - even if it doesn't exist at deployment time.

Another example might be for an Azure Functions app. A very common scenario is for a function to receive a file as input, transform it in some way, and save it to blob storage. Rather than using the root access keys for the storage account, we could create a SAS token and add it to the Azure Functions app's config settings, like in this example ARM template.

How It Works

ARM templates now support a new set of functions for generating SAS tokens. For Azure Storage, there are two types of SAS tokens - account and service - and the listAccountSas and listServiceSas functions correspond to these, respectively.

Behind the scenes, these functions invoke the corresponding functions on the Azure Storage ARM provider API. Somewhat confusingly, even though the functions' names start with list, they are actually creating SAS tokens and not listing or working with previously created tokens. (In fact, SAS tokens are not resources that Azure can track, so there's nothing to list.)

Example

Int his post I'll show ARM templates that use a variety of types of SAS for different types of situations. For simplicity I've used the outputs section to emit the SASs, but these could easily be passed through to other parts of the template such as App Service app settings (as in this example). Also, in this post I've only dealt with blob storage SAS tokens, but the same process can easily be used for queues and tables, and even let us restrict token holders to only access to table partitions or sets of rows.

Creating a Service SAS

By using a service SAS, we can grant permissions to a specific blob in blob storage, or to all blobs within a container. Similarly we can grant permissions to an individual queue, or to a subset of entities within a table. More documentation on constructing service SASs is available hereand the details of the listServiceSas function is here.

Service SASs require us to provide a canonicalizedResource, which is just a way of describing the scope of the token. We can use the path to an individual blob by using the form /blob/, such as /blob/mystorageaccountname/images/cat.jpeg or /blob/mystorageaccountname/images/cats/fluffy.jpeg. Or, we can use the path to a container by using the form /blob/mystorageaccountname/images. The examples below show different types of canonicalizedResource property values.

Read Any Blob Within a Container

Our first SAS token will let us read all of the blobs within a given container. For this, we'll need to provide four properties.

First, we'll provide a canonicalizedResource. This will be the path to the container we want to allow the holder of the token to read from. We'll construct this field dynamically based on the ARM template input.

Second, we need to provide a signedResource. This is the type of resource that the token is scoped to. In this case, we're creating a token that works across a whole blob container and so we'll use the value c.

Third, we provide the signedPermission. This is the permission, or set of permissions, that the token will allow. There are different permissions for reading, creating, and deleting blobs and other storage entities. Importantly, listing is considered to be separate to reading, so bear this in mind too. Because we just want to allow reading blobs, we'll use the value r.

Finally, we have to provide an expiry date for the token. I've set this to January 1, 2050.

When we execute the ARM template with the listServiceSas function, we need to provide these values as an object. Here's what our object looks like:

"serviceSasFunctionValues": {
    "canonicalizedResource": "[concat('/blob/', parameters('storageAccountName'), '/', parameters('containerName'))]",
    "signedResource": "c",
    "signedPermission": "r",
    "signedExpiry": "2050-01-01T00:00:00Z"
}

And here's the ARM template - check out line 62, where the listServiceSas function is actually invoked.

Read A Single Blob

In many cases we will want to issues SAS tokens to only read a single blob, and not all blobs within a container. In this case we make a couple of changes from the first example.

First, our canonicalizedResource now will have the path to the individual blob, not to a container. Again, we'll pull these from the ARM template parameters.

Second, the signedResource is now a blob rather than a container, so we use the value b instead of c.

Here's our properties object for this SAS token:

"serviceSasFunctionValues": {
    "canonicalizedResource": "[concat('/blob/', parameters('storageAccountName'), '/', parameters('containerName'), parameters('blobName'))]",
    "signedResource": "b",
    "signedPermission": "r",
    "signedExpiry": "2050-01-01T00:00:00Z"
}

And here's the full ARM template:

Write A New Blob

SAS tokens aren't just for reading, of course. We can also create a token that will allow creating or writing to blobs. One common use case is to allow the holder of a SAS to create a new blob, but not to overwrite anything that already exists. To create a SAS for this scenario, we work back at the container level again - so the canonicalizedResource property is set to the path to the container, and the signedResource is set to c. This time, we set signedPermission to c to allow for blobs to be created. (If we wanted to also allow overwriting blobs, we could do this by setting signedPermission to cw.)

Here's our properties object:

"serviceSasFunctionValues": {
  "canonicalizedResource": "[concat('/blob/', parameters('storageAccountName'), '/', parameters('containerName'))]",
  "signedResource": "c",
  "signedPermission": "c",
  "signedExpiry": "2050-01-01T00:00:00Z"
}

And here's an ARM template:

Creating an Account SAS

An account SAS works at the level of a storage account, rather than at the item-level like a service SAS. For the majority of situations you will probably want a service SAS, but account SASs can be used for situations where you want to allow access to all blobs within a storage account, or if you want to allow for the management of blob containers, tables, and queues. More detail on what an account SAS can do is available here.

More documentation on constructing account SASs is available hereand the details of the listAccountSas function is here.

Read All Blobs in Account

We can use an account SAS to let us read all of the blobs within a storage account, regardless of the container they're in. For this token we need to use signedServices = b to indicate that we're granting permissions within blob storage, and we'll use signedPermission = r to let us read blobs.

The signedResourceTypes parameter is available on account SAS tokens but not on service SAS tokens, and it lets us specify the set of APIs that can be used. We'll use o here since we want to read all blobs, and blobs are considered to be objects, and reading blobs would involve object-level API calls. There are also two other values for this parameter - s indicates service-level APIs (such as creating new blob containers), and c indicates container-level APIs (such as deleting a blob container that already exists). You can see the APIs available within each category in the Azure Storage documentation.

So our SAS token will be generated with the following properties:

"accountSasFunctionValues": {
    "signedServices": "b",
    "signedPermission": "r",
    "signedResourceTypes": "o",
    "signedExpiry": "2050-01-01T00:00:00Z"
}

Here's the full ARM template that creates this SAS:

List Blob Containers in Account

Finally, let's create a SAS token that will allow us to list the blob containers within our account. For this token we need to use signedServices = b again, but this time we'll use signedPermission = l (since l indicates permission to list).

Somewhat non-intuitively, the API that lists containers is considered a service-level API, so we need to use signedResourceTypes = s.

This means the parameters we're using to generate a SAS token are as follows:

"accountSasFunctionValues": {
    "signedServices": "b",
    "signedPermission": "l",
    "signedResourceTypes": "s",
    "signedExpiry": "2050-01-01T00:00:00Z"
}

Here's the full ARM template that creates this SAS:

You can test this by executing a GET request against this URL: https://.

Other Services

Currently it doesn't appear that other services that use SAS tokens support this feature. For example, Service Bus namespacesEvent Hubs namespaces, and Cosmos DB database accounts don't support any list* operations on their resource provider APIs that would allow for creating SAS tokens. Hopefully these will come soon, since this feature is very powerful and will allow for ARM templates to more self-contained.

Also, I have noticed that some services don't like the listAccountSas function being embedded in their resource definitions. For example, Azure Scheduler (admittedly soon to be retired) seems to have a bug where it doesn't like SAS tokens generated in this way for scheduler jobs. However, I have used this feature in other situations without any such issues.