Why it's important to use correct JSON schema with ARM templates

why_correct_json_schema_is_important_with_azure_resource_manager

I ran into an issue a little while ago related to the deployment of an Azure Resource Manager Template and the use of domain credentials within it. Specifically, the extension wouldn't deploy correctly for any resource that required domain credentials to function correctly.

The other less fun part of the issue was that of Visual Studio, my template editor of choice; there were no apparent validation issues with my code, beyond the credentials themselves not being passed correctly.

After much troubleshooting, rampant Google'ing and trying to figure out why the credentials (which I know were correct) were failing in any instance that required a domain join or use of a credential object beyond the local administrator credential, I realised I was using a mixture of two JSON Schemas for the PowerShell DSC extension.

Obvious in hindsight… right? Not so much, at least not for me. As it stands there are currently two supported JSON schemas for deploying the PowerShell.DSC VM extension that both work. The old way and the new way.

 

JSON Schemas – What are they?

JSON Schema are the building blocks from which the Azure Resource Manager (ARM) objects are derived, similar in affect to objects in Active Directory (AD) that derive their sets of properties from the schema partition.

These schemas determine the properties and parameters are required to correctly provision objects.

 

Why do JSON Schemas matter here?

Different versions of JSON Schema require different syntax; and in the case of DSC the updated schema is noticeably different but just similar enough that unless you're looking closely you may not notice that you've got it wrong; particularly when you're working across multiple templates that involve the re-use of existing code snippets.

It's always recommended that you use the latest schema for any object in Azure. However, you can continue to use the old schema if you wish. The key point here is NOT TO TRY AND USE A MIXTURE OF BOTH at the same time.

Not only does it not work, it also won't tell you why it's wrong. Why won't it tell you is a good question, but as far as I can tell when you're using Visual Studio (or any other IDE) it validates the syntax for the JSON is correct, not necessarily all of the schema for the object you are parsing.

 

Where I got caught out

Below is a code snippet from an AD FS installation DSC extension that I was working on to install and configure and AD FS farm on a couple of Virtual Machines. The actual purpose of the DSC Extension is irrelevant for the moment as it doesn't work, but I'll use this and some other examples to highlight why this failed and the right way to do it.

Firstly – a quick cheat sheet on the new way and old way is covered on this post on the Windows PowerShell Blog.

 

Example DSC Extension Settings using the new format

"settings": {
"wmfVersion": "latest",
"configuration": {
"url": "http://validURLToConfigLocation",
"script": "ConfigurationScript.ps1",
"function": "ConfigurationFunction"
},
"configurationArguments": {
"argument1": "Value1",
"argument2": "Value2"
},
"configurationData": {
"url": "https://foo.psd1"
},
"privacy": {
"dataCollection": "enable"
},
"advancedOptions": {
"forcePullAndApply": false,
"downloadMappings": {
"specificDependencyKey": "https://myCustomDependencyLocation"
}
}
}, "protectedSettings": {
"configurationArguments": {
"parameterOfTypePSCredential1": {
"userName": "UsernameValue1",
"password": "PasswordValue1"
},
"parameterOfTypePSCredential2": {
"userName": "UsernameValue2",
"password": "PasswordValue2"
}
},
"configurationUrlSasToken": "?g!bber1sht0k3n",
"configurationDataUrlSasToken": "?dataAcC355T0k3N"
}

 

 

Example DSC Extension Settings using the old format

"settings": {
"WMFVersion": "latest",
"ModulesUrl": "https://UrlToZipContainingConfigurationScript.ps1.zip",
"SasToken": "SAS Token if ModulesUrl points to private Azure Blob Storage",
"ConfigurationFunction": "ConfigurationScript.ps1\\ConfigurationFunction",
"Properties": {
"ParameterToConfigurationFunction1": "Value1",
"ParameterToConfigurationFunction2": "Value2",
"ParameterOfTypePSCredential1": {
"UserName": "UsernameValue1",
"Password": "PrivateSettingsRef:Key1"
},
"ParameterOfTypePSCredential2": {
"UserName": "UsernameValue2",
"Password": "PrivateSettingsRef:Key2"
}
},
"Privacy": {
"DataCollection": "enable"
},
"AdvancedOptions": {
"DownloadMappings": {
"specificDependencyKey": "https://myCustomDependencyLocation"
}
}
}, "protectedSettings": {
"Items": {
"Key1": "PasswordValue1",
"Key2": "PasswordValue2"
},
"DataBlobUri": "https://UrlToConfigurationDataWithOptionalSasToken.psd1"
}

 

My DSC Extension Settings

"settings": {
"configuration": {
"url": "[concat(parameters('_artifactsLocation'), '/', variables('ConfigureADFSsArchiveFolder'), '/', variables('ConfigureADFSsArchiveFileName'))]",
"script": "ConfigureADFS.ps1",
"function": "Main"
},
"configurationArguments": {
"nodeName": "[concat(variables('vmName'),copyIndex())]",
"adDomainCredential": {
"userName": "[parameters('DomainUserName')]",
"password": "PrivateSettingsRef:Password"
},

"loadbalancerIP": "[reference(resourceId('Microsoft.Network/loadBalancers',variables('loadbalancerName')),'2015-06-15').frontendIPConfigurations[0].properties.privateIpAddress]",
"adfsDisplayName": "[parameters('adfsDisplayName')]",
"adfsFqdn": "[parameters('adfsFqdn')]",
"adfsServiceAccount": "[parameters('adfsServiceAccount')]",
"adfsPfxThumbprint": "[parameters('adfsPfxThumbprint')]",
"adfsPfxuri": "PrivateSettingsRef:adfspfxUri",
"adfsPfxPassword": {
"username": "admin",
"password": "PrivateSettingsRef:pfxPassword"
}
},
"configurationData": {
"url": "[concat(parameters('_artifactsLocation'), '/', variables('ConfigureADFSsArchiveFolder'), '/', variables('ConfigureADFSConfigurationDataFileName'))]"
}
},
"protectedSettings": {
"configurationUrlSasToken": "[parameters('_artifactsLocationSasToken')]",
"configurationDataUrlSasToken": "[parameters('_artifactsLocationSasToken')]",
"Items": {
"password": "[parameters('domainPassword')]",
"pfxPassword": "[parameters('adfsPfxPassword')]",
"adfsPfxUri": "[concat(variables('adfsPfxUri'),parameters('_artifactsLocationSasToken'))]"
}
}

 

The highlighted regions represent the errors in my DSC extension. The new schema doesn't use the privatesettingsRef parameter; instead it captures any credential or private settings in their entirety.

The credential structure I've used in the DSC extension is a mixture of the two and therefore incorrect. The example below illustrates what it should look like.

 

  "settings": {
"configuration": {
"url": "[concat(parameters('_artifactsLocation'), '/', variables('ConfigureADFSsArchiveFolder'), '/', variables('ConfigureADFSsArchiveFileName'))]",
"script": "ConfigureADFS.ps1",
"function": "Main"
},
"configurationArguments": {
"nodeName": "[concat(variables('vmName'),copyIndex())]",
"loadbalancerIP": "[reference(resourceId('Microsoft.Network/loadBalancers',variables('loadbalancerName')),'2015-06-15').frontendIPConfigurations[0].properties.privateIpAddress]",
"adfsDisplayName": "[parameters('adfsDisplayName')]",
"adfsFqdn": "[parameters('adfsFqdn')]",
"adfsServiceAccount": "[parameters('adfsServiceAccount')]",
"adfsPfxThumbprint": "[parameters('adfsPfxThumbprint')]"
},
"configurationData": {
"url": "[concat(parameters('_artifactsLocation'), '/', variables('ConfigureADFSsArchiveFolder'), '/', variables('ConfigureADFSConfigurationDataFileName'))]"
}
},
"protectedSettings": {
"configurationUrlSasToken": "[parameters('_artifactsLocationSasToken')]",
"configurationDataUrlSasToken": "[parameters('_artifactsLocationSasToken')]",

"configurationArguments": {
"adfsPfxUri": "[concat(variables('adfsPfxUri'),parameters('_artifactsLocationSasToken'))]",
"adfsPfxPassword": {
"username": "admin",
"password": "[parameters('adfsPfxPassword')]"
},
"adDomainCredential": {
"userName": "[parameters('DomainUserName')]",
"password": "[parameters('domainPassword')]"
}
}
}
}

What's the moral of the story?

The lesson learned is simple really - use one or the other, but never both.

Have any other questions on Azure Resource Manager or JSON Schema? Don't be shy - reach out to the team at Xello for a more detailed technical walkthrough.

 

Hayden Fitzgerald
Author: Hayden Fitzgerald