Last week I was busy updating the certificates stored in Azure KeyVault for a project I’m working on. Previously we added the certificates that were referenced in App Services as secrets in the KeyVault with a application/x-pkcs12 type.

We now wanted to change those to real certificates, so the renewal of the certificates can be managed from Azure KeyVault.

After storing the certificates in KeyVault and modifying the ARM templates for the certificate resources to reference the new secretNames I ran into an ARM deployment error with the following message: “The parameter KeyVaultId & KeyVaultSecretName has an invalid value.”.

It turns out the new certificate we were referencing was already newer than the certificate stored in the previously referenced secret. Apparently this gives the very cryptic error message above. The solution is to make sure the certicates are the same, before deploying the ARM template with the updated secretname.

You can download the original certificate as .pfx with some Powershell you can find here.

After that import the .pfx into the new KeyVault certificate with the Import-AzureKeyVaultCertificate cmdlet.

Now you can redeploy the ARM template to update the keyVaultSecretName. After that you can update the certificate again.

In a previous post I wrote about adding one or more specified ranges of IP addresses to the IP security restrictions of an App Service.

In this post, I would like to take it one step further: add the possible outbound IP addresses of another App Service to the white list.

N.B. Securing a web app in this way is not a total security solution, because App Services of other Azure customers can share the same outbound IP addresses within the shared network infrastructure. If that’s a requirement, you will need to resort to the isolated App Service Environment, but that’s in a different pricing level.

But how do you do that? You can off course get the outbound IP addresses from another App Service by using the reference function within a template. But this is a comma separated list of addresses. So you will need to split this into an array and add the subnet mask (in CIDR notation) and action properties to use it as a property in the sites/config/ipSecurityRestrictions element I’ve described in a previous post. Things get more difficult when you realize you can only use the reference function in the resources of output section of a template. So there’s no way you can create a variable that gets the list (as a comma separated string) and splits it in to an array you can use further on in the template when deploying resources.

Enter multiple LinkedTemplates and a bit of useful information:

A template doesn’t need to deploy a resource

So this means you can create a template, that takes a resourceId for an App Service, gets the range of possible outbound IPs and returns it as an array:

But this doesn’t create an object array which we can pass to the template that adds the ranges to an App Service as described in the previous post.

So we create another template that takes an array of strings with the different IP’s and returns an array with the object in the required form:

We can then modify the first template to call this template and instead return the result of that template:

All that remains now, is to call this template from the main template that deploys the App Service and pass this to the template that actually adds the ranges to the whitelist:

At my current customer, we’re working on securing the services in Azure. One of the requirements includes setting IP-filtering in different App Services.

This can be done in ARM by setting a property ipSecurityRestrictions property of the sites/config element for an App Service. You need to assign an array of items that include the ipAddress (in CIDR format), action (Drop, Allow), priority (integer) and a name (optional, but useful for recognition of a rule in the networking blade of the Azure Portal).

The ranges are different for each service (accessible from Web Application Firewall, through internal proxy, other hostingprovider), but because the webapps that are deployed have a combination of different ranges we would like to administer the ranges in one central location.

Ideally, we can set a parameter for the LinkedTemplate that creates the App Service containing the ranges we want to whitelist.

We solved that in the following way:

Create a template with a variable per range you want to support (ip’s are chosen random):

"variables": {
    "EmptyRange": [],
    "LocalOutgoingRange": [
      {
        "ipAddress": "133.144.155.0/23",
        "action": "Allow",
        "name": "SGRange"
      }
    ],
    "WAFRange": [
      {
        "ipAddress": "99.100.101.0/21",
        "action": "Allow",
        "name": "WAFRange"
      },
      {
        "ipAddress": "100.100.100.0/19",
        "action": "Allow",
        "name": "WAFRange"
      }
    ]

Add a parameter to the template that contains the name of one or more ranges the deployment needs to add:

 "parameters": {
    "ipWhitelistingSets": {
      "type": "string",
      "defaultValue": "",
      "metadata": {
        "description": "Comma separated list of values LocalOutgoingRange,WAFRange"
      }
    },

Next, create two variables. the first one splits the parameter ipWhitelistingSets into an array. The next one uses this to conditionally concat the values of the ranges. If the range is not specified, the empty array is concatted, basically a NOOP.

 	"RangesToUse": "[split(parameters('ipWhitelistingSets'), ',')]",
    "ConcattedRange": "[concat(if(contains(variables('RangesToUse'), 'LocalOutgoingRange'), variables('LocalOutgoingRange'), variables('EmptyRange')), if(contains(variables('RangesToUse'), 'WAFRange'), variables('WAFRange'), variables('EmptyRange'))]",

We can then use this variable to loop through and set the rules in the webapp:

"resources": [
    {
      "comments": "IP whitelisting",
      "apiVersion": "2018-02-01",
      "location": "[resourceGroup().location]",
      "condition": "[not(empty(variables('RangesToUse')))]",
      "type": "Microsoft.Web/sites/config",
      "name": "[concat(parameters('webAppName'), '/web')]",
      "properties": {
        "mode": "Incremental",
        "copy": [
          {
            "name": "ipSecurityRestrictions",
            "count": "[length(variables('ConcattedRange'))]",
            "input": {
              "ipAddress": "[variables('ConcattedRange')[copyIndex('ipSecurityRestrictions')].ipAddress]",
              "action": "[variables('ConcattedRange')[copyIndex('ipSecurityRestrictions')].action]",
              "priority": "[copyIndex('ipSecurityRestrictions', 1)]",
              "name": "[variables('ConcattedRange')[copyIndex('ipSecurityRestrictions')].name]"
            }
          }
        ]
      }
    },

After this, we can call our template from the template that creates the App Service to set the ip restrictions:

{
      "comments": "Whitelisting WebApp Elements",
      "apiVersion": "2015-01-01",
      "name": "[concat(concat(parameters('webAppName'), 'wl'))]",
      "type": "Microsoft.Resources/deployments",
      "condition": "[not(empty(parameters('ipWhitelistingSets')))]",
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "[variables('ipWhitelistingTemplateUrl')]",
          "contentVersion": "1.0.0.0"
        },
        "parameters": {
          "webAppName": {
            "value": "[parameters('webAppName')]"
          },
          "ipWhitelistingSets": {
            "value": "LocalOutgoingRange,WAFRange"
          }          
        }
      },
      "dependsOn": [
        "[parameters('webAppName')]"
      ]
    }

Now we can use the same template to set different sets of restrictions for different App Services. If the IP ranges change, we have one location to modify the ranges and can simply deploy the resources again.

Summary: at the moment of writing, you cannot use Data Lake Storage Gen2 storage account as a target for Application Insights continuous export.

Currently Azure Data Lake Storage Gen2 is in preview.

Because I want to use Big Data analytics on Application Insights information I’ve set up a pipeline through Stream analytics in the past to get the application insights data into Azure Data Lake. Now that Gen2 is available I wanted to see if you can export from Application Insights into Data Lake Storage Gen2 straight away, bypassing stream analytics or another component to transfer the data. So lets try:

From the portal, when creating a Storage account, you can specify you want to enable Data Lake Storage Gen2:

Unfortunately, when setting up continuous export to this storage account, you cannot select a location, because creating a container is not possible:

The exclamation mark indicates the container name is invalid, but it does match the requirements.

When trying to create a container through Azure Storage Explorer, you get a more descriptive error:

So unfortunately, you cannot use continuous export to Data Lake Storage Gen2 right now. So we’re still stuck with using stream analytics or Azure Data Factory.

At a customer we’ve set up an Infrastructure-As-Code solution with Azure Resource Manager (ARM) for deploying Azure Resources through a Azure DevOps build and release pipelines.

To use LinkedTemplates in ARM you need to provide a URI to the template in the calling template:

 "templateLink": { 
 	"uri":"https://mystorageaccount.blob.core.windows.net/AzureTemplates/newStorageAccount.json",           
    "contentVersion":"1.0.0.0"        
   }

This needs to be a location where Azure Resource Manager can download the template, without authentication. But what if you don’t want to make your LinkedTemplates available for anyone? You can store them in a non public storage container, but use a SAS token. If you pass the URL plus SAS token to the calling template. It can use that to download the template. More about that here.

We split all resource components up into separate templates, e.g. the ipSecurityRestrictions part for a Web App is in a separate template, which need to be called from the generic web app template we’ve created. I don’t want to have to create parameters for the SAS token into each Linked Template I want to call.

So I decided to use a different solution for creating the URI:

"variables": {  
 "ipWhitelistingTemplateUrl": "[replace(deployment().properties.templateLink.uri, '/WebApp-Generic.json?', '/WebApp-IpWhitelisting.json?')]"
}

It’s not complicated and you can discuss if this is indeed a better solution. But I think it demonstrates the different possibilities in ARM templates.