Dilignet Blog - How does Cloud solve your problems?

The Importance of Correct Schema Usage with ARM Templates

dilignet Empower Employees, Optimise Operations 0 Comments

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 to 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, banging, rampant googling and out-and-out despair 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 that 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 and why do they matter in this instance?

JSON Schema are the building blocks from which the Azure Resource Manager objects are derived (similar in affect to objects in Active Directory 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 they matter? As I stated above, different versions 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, 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:

So 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 the old way is summarised on this excellent 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?

Simple really, use one or the other but never both.

Cheers,

Hayden Fitzgerald
Principal Consultant

 

Leave a Reply