© Stuart Preston 2016

Stuart Preston, Using Chef with Microsoft Azure, 10.1007/978-1-4842-1476-3_5

5. Advanced Chef Provisioning Techniques

Stuart Preston

(1)London, UK

In this chapter we’re going to have a look at some of the more advanced things you might want to do when provisioning resources in Azure via Chef Provisioning. We’ll start by looking at how to ensure your secrets can be stored securely using Azure’s Key Vault PaaS service, use that service to securely configure WinRM on a VM running in Azure, and then look at some ways in which you can leverage some of the newest PaaS services in Azure.

Explaining VM Image Naming within Azure Resource Manager JSON

In the previous chapter we saw how we could use an Azure Resource Manager template to create a Windows Server compute resource, but how did we know which version of Windows Server to deploy? The basic answer is that the image used was baked into the template. But what if we want to use a different version of Windows Server? That’s what we’ll explain shortly.

We saw in the template we were using, downloaded from https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-with-rdp-port , that the Virtual Machine is a type of Compute resource that has some internal variables that include publisher, offer, sku, and version. Here’s the fragment of template that defines the Virtual Machine:

{
  "apiVersion": "[variables('apiVersion')]",
  "type": "Microsoft.Compute/virtualMachines",
  "name": "vm",
  "location": "[variables('location')]",
  "dependsOn": [
    "[resourceId('Microsoft.Storage/storageAccounts',variables('storageAccountName'))]",
    "[concat('Microsoft.Network/networkInterfaces/',parameters('vmName'),'-nic')]"
  ],
  "properties": {
    "hardwareProfile": {
      "vmSize": "Standard_D2"
    },
    "osProfile": {
      "computerName": "[parameters('vmName')]",
      "adminUsername": "[parameters('adminUserName')]",
      "adminPassword": "[parameters('adminPassword')]"
    },
    "storageProfile": {
      "imageReference": {
        "publisher": "[variables('imagePublisher')]",
        "offer": "[variables('imageOffer')]",
        "sku": "[variables('imageSku')]",
        "version": "latest"
      },
      "osDisk": {
        "name": "osdisk",
        "vhd": {
          "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/vhds/',parameters('vmName'),'-osdisk.vhd')]"
        },
        "caching": "ReadWrite",
        "createOption": "FromImage"
      }
    },
    "networkProfile": {
      "networkInterfaces": [
        {
          "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(parameters('vmName'),'-nic'))]"
        }
      ]
    },
    "diagnosticsProfile": {
      "bootDiagnostics": {
         "enabled": "true",
         "storageUri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net')]"
      }
    }
  }
}

The template above expects these properties to be provided to it, so how do we discover the correct values for the template? Let’s have a look at how to retrieve the publishers, offers, and skus for any public VM image.

Identifying and Retrieving VM Images

There’s a particular sequence of events you must go through in order to list the publicly available VM images :

  • To get a list of the Publishers you need to execute azure vm image list-publishers (Azure-cli) or Get-AzureRmVMImagePublisher (PowerShell).

  • Then you can retrieve the Offers with azure vm image list-offers (Azure-cli) or Get-AzureRmVMImageOffer (PowerShell).

  • Only then can you retrieve the Sku with azure vm image list-skus (Azure-cli) or Get-AzureRmVMImageSku (PowerShell).

As the specific images available can possibly vary by region, the datacenter location must be passed in as a parameter to each command or cmdlet too. Let’s step through the process of ’discovering’ the Windows 2012 R2 Datacenter image in both Azure-cli and PowerShell, noting that I am using West Europe as my region (as it is my nearest datacenter).

Azure-cli

To discover images using Azure-cli we use the following commands:

PS C:UsersStuartPreston> azure vm image list-publishers "West Europe"                
info:    Executing command vm image list-publishers
+ Getting virtual machine and/or extension image publishers (Location: "westeurope")
data:    Publisher                                             Location
data:    ----------------------------------------------------  ----------
[...]


data:    MicrosoftWindowsServer                                westeurope

[...]
info:    vm image list-publishers command OK

Now we can use the azure vm image list-offers command and specify the publisher:

PS C:UsersStuartPreston> azure vm image list-offers "West Europe" MicrosoftWindowsServer              
info:    Executing command vm image list-offers
+ Getting virtual machine image offers (Publisher: "MicrosoftWindowsServer" Location:"westeurope")
data:    Publisher               Offer          Location
data:    ----------------------  -------------  ----------
data:    MicrosoftWindowsServer  WindowsServer  westeurope
info:    vm image list-offers command OK

Now armed with the Publisher and the Offer we can retrieve the Skus:

PS C:UsersStuartPreston> azure vm image list-skus "West Europe" MicrosoftWindowsServer WindowsServer              
info:    Executing command vm image list-skus
+ Getting virtual machine image skus (Publisher:"MicrosoftWindowsServer" Offer:"WindowsServer" Location:"westeurope")
data:    Publisher               Offer          sku                                       data:    ----------------------  -------------  ---------------------------------------- data:    MicrosoftWindowsServer  WindowsServer  2008-R2-SP1                              
data:    MicrosoftWindowsServer  WindowsServer  2012-Datacenter                           data:    MicrosoftWindowsServer  WindowsServer  2012-R2-Datacenter                        data:    MicrosoftWindowsServer  WindowsServer  2016-Technical-Preview-3-with-Containers  data:    MicrosoftWindowsServer  WindowsServer  2016-Technical-Preview-4-Nano-Server      data:    MicrosoftWindowsServer  WindowsServer  2016-Technical-Preview-Nano-Server        data:    MicrosoftWindowsServer  WindowsServer  Windows-Server-Technical-Preview          info:    vm image list-skus command OK

Let’s see how to do the same using PowerShell.

PowerShell

To discover images using PowerShell we use the following commands:

PS C:UsersStuartPreston> Get-AzureRmVMImageOffer -Location "West Europe"              
PublisherName
-------------
[...]
MicrosoftWindowsServer

Now we can use the Get-AzureRmVMImageOffer command and specify the publisher:

PS C:UsersStuartPreston> Get-AzureRmVMImageOffer -Location "West Europe" -PublisherName "MicrosoftWindowsServer" | Select Offer              
Offer
-----
WindowsServer

Armed with the Publisher and the Offer we can retrieve the list of available Skus:

PS C:UsersStuartPreston> Get-AzureRMImageSku -Location "West Europe" -PublisherName "MicrosoftWindowsServer" -Offer "WindowsServer" | Select PublisherName, Offer, Skus              
PublisherName           Offer          Skus
-------------           -----          ----
MicrosoftWindowsServer  WindowsServer  2008-R2-SP1
MicrosoftWindowsServer  WindowsServer  2012-Datacenter
MicrosoftWindowsServer  WindowsServer  2012-R2-Datacenter
MicrosoftWindowsServer  WindowsServer  2016-Technical-Preview-3-with-Containers
MicrosoftWindowsServer  WindowsServer  2016-Technical-Preview-4-Nano-Server
MicrosoftWindowsServer  WindowsServer  2016-Technical-Preview-Nano-Server
MicrosoftWindowsServer  WindowsServer  Windows-Server-Technical-Preview

Now we have all the constituent values, these can be passed in as parameters in your provisioning recipes or used for other purposes, such as with Test Kitchen (you can read more about this in chapter 6).

Using Azure Key Vault to Store Secrets

As we know, it’s not just VMs that you can create with Azure Resource Manager. We can also provision PaaS resources too. One of the PaaS solutions within Azure is KeyVault (see https://azure.microsoft.com/en-us/services/key-vault/ ), which is best described as a scalable Key Management solution that allows the storage of cryptographic keys without the cost normally associated with the implementation of HSMs (Hardware Security Modules) on premises.

The retrieval of a certificate from Key Vault is currently required if you wish to enable WinRM in a secure configuration on a Windows Server at provisioning time. So let’s cover how to provision the Key Vault itself, along with the command-line tools used to manage it. Later examples in this chapter will show you how to provision machines that rely on secrets stored in the Key Vault.

You may create many Key Vault per subscription if you wish. Let’s go through the process of provisioning a Key Vault. Azure Key Vault is quite simple to provision using Azure-cli, PowerShell, or Chef Provisioning, so you may be asking yourself why you would want to use Chef. The main reason to do so is for consistency, so that we can keep all our provisioned resources in the same repo. Now let’s go through the process of provisioning a Key Vault using Chef.

As this is the first example in this chapter, we’ll need a repo in which to work in:

PS C:UsersStuartPreston> chef generate app chefazure-ch05          
Compiling Cookbooks...
Recipe: code_generator::app
[output truncated]

You’ll now want to copy in the .chef folder from our chef-starter repo (as created in chapter 1), so that our keys and knife.rb configuration are available in our new repo.

Now we can enter our repo and open the folder in our code editor:

PS C:UsersStuartPreston> cd chefazure-ch05          
PS C:UsersStuartPrestonchefazure-ch05> code .

Azure Key Vault ARM Template

We need to create a file cookbooks/chefazure-ch05/files/keyvault/deploy.json within our repository representing the ARM template that creates a Key Vault so that we can refer to it from a provisioning recipe.

Note

This ARM template can be downloaded from https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-key-vault-create/azuredeploy.json , and you can find this and the associated recipes in the book's accompanying download.

Here’s the template reproduced in its entirety. We can see there are nine parameters that I have highlighted in bold that we will need to supply in our Chef Provisioning recipe and finally the resource contained within it:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "keyVaultName": {
      "type": "string",
      "metadata": {
        "description": "Name of the Vault"
      }
    },
    "location": {
      "type": "string",
      "allowedValues": [
        "Central US",
        "East US",
        "East US 2",
        "North Central US",
        "South Central US",
        "West US",
        "North Europe",
        "West Europe",
        "East Asia",
        "Southeast Asia",
        "Japan East",
        "Japan West",
        "Brazil South",
        "Australia East",
        "Australia Southeast"
      ],
      "metadata": {
        "description": "Key Vault location"
      }
    },
    "tenantId": {
      "type": "string",
      "metadata": {
        "description": "Tenant Id of the subscription. Get using Get-AzureSubscription cmdlet or Get Subscription API"
      }
    },
    "objectId": {
      "type": "string",
      "metadata": {
        "description": "Object Id of the AD user. Get using Get-AzureADUser or Get-AzureADServicePrincipal cmdlets"
      }
    },
    "keysPermissions": {
      "type": "array",
      "defaultValue": [ ],
      "metadata": {
        "description": "Permissions to keys in the vault. Valid values are: all, create, import, update, get, list, delete, backup, restore, encrypt, decrypt, wrapkey, unwrapkey, sign, and verify."
      }
    },
    "secretsPermissions": {
      "type": "array",
      "defaultValue": [ ],
      "metadata": {
        "description": "Permissions to secrets in the vault. Valid values are: all, get, set, list, and delete."
      }
    },
    "skuName": {
      "type": "string",
      "defaultValue": "Standard",
      "allowedValues": [
        "Standard",
        "Premium"
      ],
      "metadata": {
        "description": "SKU for the vault"
      }
    },
    "enableVaultForDeployment": {
      "type": "bool",
      "defaultValue": false,
      "allowedValues": [
        true,
        false
      ],
      "metadata": {
        "description": "Specifies if the vault is enabled for a VM deployment"
      }
    },
    "enableVaultForDiskEncryption": {
      "type": "bool",
      "defaultValue": false,
      "allowedValues": [
        true,
        false
      ],
      "metadata": {
        "description": "Specifies if the azure platform has access to the vault for enabling disk encryption scenarios."
      }
    }
  },
  "resources": [
    {
      "type": "Microsoft.KeyVault/vaults",
      "name": "[parameters('keyVaultName')]",
      "apiVersion": "2015-06-01",
      "location": "[parameters('location')]",
      "properties": {
        "enabledForDeployment": "[parameters('enableVaultForDeployment')]",
        "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]",
        "tenantId": "[parameters('tenantId')]",
        "accessPolicies": [
          {
            "tenantId": "[parameters('tenantId')]",
            "objectId": "[parameters('objectId')]",
            "permissions": {
              "keys": "[parameters('keysPermissions')]",
              "secrets": "[parameters('secretsPermissions')]"
            }
          }
        ],
        "sku": {
          "name": "[parameters('skuName')]",
          "family": "A"
        }
      }
    }
  ]
}

Retrieving the Object ID for an Azure Active Directory User

When we create the Key Vault, we will need to add permissions so that our normal user (not the application Service Principal) has access to be able to create keys. Luckily this is something that the Key Vault provider can do for us. To configure that, we need the Object Id of the Azure Active Directory user we want to grant access to, which can be retrieved using Azure-cli and PowerShell.

Azure-cli

In Azure-cli we can use the azure ad user show --upn command and pass in the UPN of the user we want to grant access to:

PS C:UsersStuartPrestonchefazure-ch05> azure ad user show --upn [email protected]              
info:    Executing command ad user show
+ Getting active directory user
data:    Object Id:       38e8a50f-YOUR-GUID-HERE-a605e06e9695
data:    Principal Name:  [email protected]
data:    Display Name:    Stuart Preston
data:    E-Mail:          [email protected]
data:

PowerShell

In PowerShell we can use the Get-AzureRmADUser -UserPrincipalName cmdlet and pass in the UPN of the user we want to grant access to:

PS C:UsersStuartPreston> Get-AzureRmADUser -UserPrincipalName [email protected]              
DisplayName                    ObjectId
-----------                    --------
Stuart Preston                 38e8a50f-YOUR-GUID-HERE-a605e06e9695

Azure Key Vault Provisioning Recipe

To provision our Key Vault, we need to create a new file to contain our recipe in the following path: cookbooks/chefazure-ch05/recipes/keyvault.rb and add the below recipe to it. Now that we have the ObjectId for the user who should have permission to the Key Vault, this can be substituted into the objectId parameter.

Tip

If you cannot locate your TenantId, the quickest way might be to have a look in your credentials file located at ∼/.azure/credentials.

require 'chef/provisioning/azurerm'
with_driver 'AzureRM:b6e7eee9-YOUR-GUID-HERE-03ab624df016'


azure_resource_group 'chefazure-shared' do
  location 'West Europe'
  tags CreatedFor: 'Using Chef with Microsoft Azure book'
end


azure_resource_template 'keyvault-deployment' do
  resource_group 'chefazure-shared'
  template_source 'cookbooks/chefazure-ch05/files/keyvault/deploy.json'
  parameters keyVaultName: 'chefazure-keyvault',
             location: 'West Europe',
             tenantId: '48b9bba3-YOUR-GUID-HERE-90f0b68ce8ba',
             objectId: '38e8a50f-YOUR-GUID-HERE-a605e06e9695',
             keysPermissions: ['all'],
             secretsPermissions: ['all'],
             skuName: 'Standard',
             enableVaultForDeployment: true,
             enableVaultForDiskEncryption: false
end

Upload the cookbook using knife cookbook upload:

PS C:UsersStuartPrestonchefazure-ch05> knife cookbook upload chefazure-ch05            
Uploading chefazure-ch05 [0.1.0]
Uploaded 1 cookbook.

Now let’s provision this recipe using our local chef-client:

PS C:UsersStuartPrestonchefazure-ch05> chef-client -o recipe[chefazure-ch05::keyvault]
Starting Chef Client, version 12.5.1
[2015-11-21T17:20:24+00:00] WARN: Run List override has been provided.
[2015-11-21T17:20:24+00:00] WARN: Original Run List: []
[2015-11-21T17:20:24+00:00] WARN: Overridden Run List: [recipe[chefazure-ch05::keyvault]]
resolving cookbooks for run list: ["chefazure-ch05::keyvault"]
Synchronizing Cookbooks:
  - chefazure-ch05 (0.1.0)
Compiling Cookbooks...
Converging 2 resources
Recipe: chefazure-ch05::keyvault
  * azure_resource_group[chefazure-shared] action create
    - create or update Resource Group chefazure-shared
  * azure_resource_template[keyvault-deployment] action deploy
    - Result: Accepted
    - Resource Template deployment reached end state of 'Succeeded'.
    - deploy or re-deploy Resource Manager template 'keyvault-deployment'
[2015-11-21T17:20:39+00:00] WARN: Skipping final node save because override_runlist was
given


Running handlers:
Running handlers complete
Chef Client finished, 2/2 resources updated in 31 seconds

We have now successfully created a Key Vault and provided access to our Service Principal. Let’s have a look at how a Windows Server might use Key Vault in the process of enabling WinRM securely.

Creating a Windows Server with WinRM Securely Enabled via Key Vault

Now that we have uploaded our certificates into a Key Vault, we can refer to the Key Vault in an Azure Resource Manager template. This opens up certain scenarios such as this one where the WinRM endpoint is correctly configured on a new VM that we create in a Resource Group. There are three stages to the process:

1. Creating a self-signed certificate

2. Uploading the self-signed certificate to the Key Vault

3. Provisioning a WinRM-enabled Windows Server using the certificate in the Key Vault

Let’s go through the process in detail.

Creating a Self-signed Certificate

To get started we need to generate a self-signed certificate to upload to our Key Vault.

Mac OS X/Linux (Azure-cli)

If you are running on Mac or Linux, we can use the OpenSSL tools to generate a certificate in PFX format by following the commands below in bold:

$ openssl genrsa 2048 > private.pem                
Generating RSA private key, 2048 bit long modulus
.................................................+++
..+++
e is 65537 (0x10001)
$ openssl req -x509 -new -key private.pem -out public.key
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:GB
State or Province Name (full name) [Some-State]:London
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Pendrica Ltd
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:vm
Email Address []:[email protected]


$ openssl pkcs12 -export -in public.key -inkey private.pem -out vm.pfx
Enter Export Password:
Verifying - Enter Export Password:

We how have a .pfx suitable for uploading to Key Vault.

Windows (PowerShell)

If you are running on Windows 8.1, Windows 2012 R2 or higher, the New-SelfSignedCertificate cmdlet can be used to generate a certificate that can then be exported to a .pfx suitable for uploading to the Key Vault:

PS C:UsersStuartPrestonchefazure-ch05> New-SelfSignedCertificate -DnsName vm.mydomain.local                

    Directory: Microsoft.PowerShell.SecurityCertificate::LocalMachineMY

Thumbprint                                Subject
----------                                -------
434A322583F2903880B27FED0E6AA1E0AB68E000  CN=vm.mydomain.local
C:UsersStuartPrestonchefazure-ch05> $certPassword = ConvertTo-SecureString -String "P2ssw0rd" -Force -AsPlainText
C:UsersStuartPrestonchefazure-ch05> $cert = Get-ChildItem -Path cert:localMachinemy | where { $_.Subject -eq 'CN=vm.mydomain.local' }
C:UsersStuartPrestonchefazure-ch05> Export-PfxCertificate -Cert $cert -Password $certPassword -FilePath vm.pfx


    Directory: C:UsersStuartPrestonchefazure-ch05

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       21/11/2015     23:20           2615 vm.pfx

Uploading the Certificate to Key Vault

To upload our certificate into the Key Vault so it is ready for use later, we can use the Azure-cli or PowerShell. Both paths follow the same approach - a temporary file is needed as our payload that we upload to Key Vault. The payload is a JSON document that contains our PFX data (which needs to be base64 encoded), and then the whole document needs to be base64 encoded before uploading it.

Mac OS X/Linux (Azure-cli)

To manually create the required payload, we’ll start by base64 encoding our PFX file:

$ base64 -i vm.pfx              
MIIKMwIBAzCCCe8GCSqGSIb3DQEHAaCCCeAEggncMIIJ2DCCBg8GCSqGSIb3DQEHAaCCBgAEggX8MIIF+DCCBfQGCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcNAQwBAzAOBAhFmO4v4OiE9wICB9AEggTYz4nFVhhVpiKqD6+DZ8TLd837
[...] full output not shown
RzEiBCBEAEUAUwBLAFQATwBQAC0AVABJAEQASgAzAFMAOAAAADA7MB8wBwYFKw4DAhoEFNZlC7N1gOrgUIuGWBuoSa31UbHcBBTtCIWsbWB/qgWl31zEK4kmwKQUbQICB9A=

Now we can create a temporary file called secret.json using the following as a template and pasting in your base64 encoded data and password if you set one:

{
"data": "[paste your base64 encoded content from above]",
"dataType" :"pfx",
"password": "[pfx password]"
}

Now we need to base64 encode this secret.json:

$ base64 -i secret.json              
ewoiZGF0YSI6ICJNSUlLSVFJQkF6Q0NDZWNHQ1NxR1NJYjNEUUVIQWFDQ0NkZ0VnZ25VTUlJSjBEQ0NCSWNHQ1NxR1NJYjNEUUVIQnFDQ0JIZ3dnZ1IwQWdFQU1JSUViUVlKS29aSWh2Y05BUWNCTUJ3R0NpcUdTSWIzRFFFTUFRWXdEZ1FJVHIv
[...] full output not shown
Yk1GaXpFTXBTWUUvODVnS0ZvSDk4T1BHc3dNVEFoTUFrR0JTc09Bd0lhQlFBRUZBdWFzdjlVM2gxRi8wL2NWK0EvSWx4eDdhMFhCQWdvc2NUbkpWMEZ4QUlDQ0FBPSIsCiJkYXRhVHlwZSI6ICJwZngiLAoicGFzc3dvcmQiOiAiIgp9Cg==

We can then use the azure keyvault secret set command to upload this secret:

$ azure keyvault secret set --vault-name "chefazure" --secret-name "vmselfcert" --value "ewoiZGF0YSI6ICJNSUlLSVFJQkF6Q0NDZWNHQ1NxR1NJYjNEUUVIQWFDQ0NkZ0VnZ25VTUlJSjBEQ0NCSWNHQ1NxR1NJYjNEUUVIQnFDQ0JIZ3dnZ1IwQWdFQU1JSUViUVlKS29aSWh2Y05BUWNCTUJ3R0NpcUdTSWIzRFFFTUFRWXdEZ1FJVHIvd0ppOE5QcHNDQWdnQWdJSUVRRHNldzZVaVM4SHg2eldOWHN2clE1WGFiMDJsNUZkOE5aZjhEeVFzOCtYOHB0VGlZNUJSOGFMaDd5UVJ                

[...] full input not shown

GaXpFTXBTWUUvODVnS0ZvSDk4T1BHc3dNVEFoTUFrR0JTc09Bd0lhQlFBRUZBdWFzdjlVM2gxRi8wL2NWK0EvSWx4eDdhMFhCQWdvc2NUbkpWMEZ4QUlDQ0FBPSIsCiJkYXRhVHlwZSI6ICJwZngiLAoicGFzc3dvcmQiOiAiIgp9Cg=="
info:    Executing command keyvault secret set
+ Creating secret https://chefazure.vault.azure.net/secrets/vmselfcert        
data:    value "ewoiZGF0YSI6ICJNSUlLSVFJQkF6Q0NDZWNHQ1NxR1NJYjNEUUVIQWFDQ0NkZ0VnZ25VTUlJSjBEQ0NCSWNHQ1NxR1NJYjNEUUVIQnFDQ0JIZ3dnZ1IwQWdFQU1JSUViUVlKS29aSWh2Y05BUWNCTUJ3R0NpcUdTSWIzRFFFTUFRWXdEZ1FJVHI
[...] full output not shown
aYk1GaXpFTXBTWUUvODVnS0ZvSDk4T1BHc3dNVEFoTUFrR0JTc09Bd0lhQlFBRUZBdWFzdjlVM2gxRi8wL2NWK0EvSWx4eDdhMFhCQWdvc2NUbkpWMEZ4QUlDQ0FBPSIsCiJkYXRhVHlwZSI6ICJwZngiLAoicGFzc3dvcmQiOiAiIgp9Cg=="
data:    id https://chefazure.vault.azure.net/secrets/vmselfcert/3772db676efd407b89f8fdd86bb545f5
data:    attributes enabled true
data:    attributes created "2016-01-28T22:34:51.000Z"
data:    attributes updated "2016-01-28T22:34:51.000Z"
info:    keyvault secret set command OK

Windows (PowerShell)

Here’s a PowerShell script to create our temporary payload in the correct format and upload it:

$fileName = "vm_mydomain_com.pfx"
$certPassword = "your-cert-password"
$fileContentBytes = get-content $fileName -Encoding Byte
$fileContentEncoded = [System.Convert]::ToBase64String($fileContentBytes)
$jsonObject = @"
{
"data": "$filecontentencoded",
"dataType" :"pfx",
"password": "$certPassword"
}
"@
$jsonObjectBytes = [System.Text.Encoding]::UTF8.GetBytes($jsonObject)
$jsonEncoded = [System.Convert]::ToBase64String($jsonObjectBytes)
$secret = ConvertTo-SecureString -String $jsonEncoded -AsPlainText -Force

Save the file as encodeCertificate.ps1 and execute it. Now we can execute the Set-AzureKeyVaultSecret cmdlet to upload the secret to the Key Vault:

PS C:UsersStuartPrestonchefazure-ch05> Set-AzureKeyVaultSecret -VaultName "chefazure" -Name "vmselfcert" -SecretValue $secret              
Vault Name   : chefazure
Name         : vmselfcert
Version      : f4233e85f0c94bd987b337a7e329fa48
Id           : https://chefazure.vault.azure.net:443/secrets/vmselfcert/f4233e85f0c94bd987b337a7e329fa48
Enabled      : True
Expires      :
Not Before   :
Created      : 22/11/2015 00:20:23
Updated      : 22/11/2015 00:20:23
Content Type :
Tags         :

We’ll need the URL to the secret (the Id field) for the next section.

Provisioning a WinRM-Enabled Windows Server

Here’s the deploy.json file you will need to provision a WinRM-enabled Windows Server. You can save this JSON in cookbooks/chefazure-ch05/files/winrm_winserver/deploy.json

{
  "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "newStorageAccountName": {
      "type": "string",
      "metadata": {
        "description": "This is the name of the storage account"
      }
    },
    "dnsNameForPublicIP": {
      "type": "string",
      "metadata": {
        "description": "DNS Name for the Public IP. Must be lowercase."
      }
    },
    "adminUserName": {
      "type": "string",
      "metadata": {
        "description": "Admin username"
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Admin password"
      }
    },
    "imagePublisher": {
      "type": "string",
      "defaultValue": "MicrosoftWindowsServer",
      "metadata": {
        "description": "Image Publisher"
      }
    },
    "imageOffer": {
      "type": "string",
      "defaultValue": "WindowsServer",
      "metadata": {
        "description": "Image Offer"
      }
    },
    "imageSKU": {
      "type": "string",
      "defaultValue": "2012-R2-Datacenter",
      "metadata": {
        "description": "Image SKU"
      }
    },
    "location": {
      "type": "String",
      "metadata": {
        "description": "Location where resources will be deployed"
      }
    },
    "vmSize": {
      "type": "string",
      "metadata": {
        "description": "Size of the VM"
      }
    },
    "vmName": {
      "type": "string",
      "metadata": {
        "description": "Name of the VM"
      }
    },
    "vaultName": {
      "type": "string",
      "metadata": {
        "description": "Name of the KeyVault"
      }
    },
    "vaultResourceGroup": {
      "type": "string",
      "metadata": {
        "description": "Resource Group of the KeyVault"
      }
    },
    "certificateUrl": {
      "type": "string",
      "metadata": {
        "description": "Url of the certificate with version in KeyVault e.g. https://testault.vault.azure.net/secrets/testcert/b621es1db241e56a72d037479xab1r7"
      }
    }
  },
  "variables": {
    "addressPrefix": "10.0.0.0/16",
    "subnet1Name": "Subnet-1",
    "subnet1Prefix": "10.0.0.0/24",
    "vmStorageAccountContainerName": "vhds",
    "publicIPAddressName": "myPublicIP",
    "publicIPAddressType": "Dynamic",
    "storageAccountType": "Standard_LRS",
    "virtualNetworkName": "myVNET",
    "nicName": "myNIC",
    "vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]",
    "subnet1Ref": "[concat(variables('vnetID'),'/subnets/',variables('subnet1Name'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[parameters('newStorageAccountName')]",
      "apiVersion": "2015-05-01-preview",
      "location": "[parameters('location')]",
      "properties": {
        "accountType": "[variables('storageAccountType')]"
      }
    },
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[variables('publicIPAddressName')]",
      "location": "[parameters('location')]",
      "properties": {
        "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
        "dnsSettings": {
          "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
        }
      }
    },
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[variables('virtualNetworkName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnet1Name')]",
            "properties": {
              "addressPrefix": "[variables('subnet1Prefix')]"
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables('nicName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
              },
              "subnet": {
                "id": "[variables('subnet1Ref')]"
              }
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[parameters('vmName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computername": "[parameters('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]",
          "secrets": [
            {
              "sourceVault": {
                "id": "[resourceId(parameters('vaultResourceGroup'), 'Microsoft.KeyVault/vaults', parameters('vaultName'))]"
              },
              "vaultCertificates": [
                {
                  "certificateUrl": "[parameters('certificateUrl')]",
                  "certificateStore": "My"
                }
              ]
            }
          ],
          "windowsConfiguration": {
            "provisionVMAgent": "true",
            "winRM": {
              "listeners": [
                {
                  "protocol": "http"
                },
                {
                  "protocol": "https",
                  "certificateUrl": "[parameters('certificateUrl')]"
                }
              ]
            },
            "enableAutomaticUpdates": "true"
          }
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[parameters('imagePublisher')]",
            "offer": "[parameters('imageOffer')]",
            "sku": "[parameters('imageSKU')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "osdisk",
            "vhd": {
              "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/vhds/','osdisk.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
            }
          ]
        },
        "diagnosticsProfile": {
          "bootDiagnostics": {
             "enabled": "true",
             "storageUri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
          }
        }
      }
    }
  ]
}

Here’s an example recipe that would be required. You can tweak it as required for your environment and save it as cookbooks/chefazure-ch05/recipes/winrm_winserver.rb.

require 'chef/provisioning/azurerm'
with_driver 'AzureRM:b6e7eee9-YOUR-GUID-HERE-03ab624df016'


azure_resource_group 'chefazure-ch05' do
  location 'West Europe'
  tags CreatedFor: 'Using Chef with Microsoft Azure book'
end


azure_resource_template 'keyvault-deployment' do
  resource_group 'chefazure-ch05'
  template_source 'cookbooks/chefazure-ch05/files/winrm_winserver/deploy.json'
  parameters newStorageAccountName: 'chefazurech05',
             dnsNameForPublicIP: 'chefazure-ch05-vm',
             adminUsername: 'azure',
             adminPassword: 'P2ssw0rd',
             imagePublisher: 'MicrosoftWindowsServer',
             imageOffer: 'WindowsServer',
             imageSKU: '2012-R2-Datacenter',
             location: 'West Europe',
             vmName: 'ch05vm',
             vmSize: 'Standard_D2',
             vaultName: 'chefazure',
             vaultResourceGroup: 'chefazure-shared',
             certificateUrl: 'https://chefazure.vault.azure.net:443/secrets/vmselfcert/02a48bca5dbf42228a170c6ebab476af'
end

We can see that this is a fairly standard ARM template that will produce a Windows 2012 R2 Datacenter server, but also configured to point at the Key Vault we created earlier. This allows the template to retrieve the certificate, store it in the VM's certificate store, and then configure WinRM securely.

Upload the cookbook using knife cookbook upload:

PS C:UsersStuartPrestonchefazure-ch05> knife cookbook upload chefazure-ch05            
Uploading chefazure-ch05 [0.1.0]
Uploaded 1 cookbook.

We can now execute this by running chef-client -o recipe[chefazure-ch05::winrm_winserver]:

PS C:UsersStuartPrestonchefazure-ch05> chef-client -o recipe[chefazure-ch05::winrm_winserver]              
Starting Chef Client, version 12.5.1
[2015-11-22T07:43:45+00:00] WARN: Run List override has been provided.
[2015-11-22T07:43:45+00:00] WARN: Original Run List: []
[2015-11-22T07:43:45+00:00] WARN: Overridden Run List: [recipe[chefazure-ch05::winrm_winserver]]
[2015-11-22T07:43:46+00:00] WARN: chef-client doesn't have administrator privileges on node DESKTOP-TIDJ3S8. This might cause unexpected resource failures.
resolving cookbooks for run list: ["chefazure-ch05::winrm_winserver"]
Synchronizing Cookbooks:
  - chefazure-ch05 (0.1.0)
Compiling Cookbooks...
Converging 2 resources
Recipe: chefazure-ch05::winrm_winserver
  * azure_resource_group[chefazure-ch05] action create
    - create or update Resource Group chefazure-ch05
  * azure_resource_template[chefazure-ch05-vm-deployment] action deploy
    - Result: Accepted
    - Resource Microsoft.Network/virtualNetworks 'myVNET' provisioning status is Running
    - Resource Microsoft.Network/publicIPAddresses 'myPublicIP' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Network/virtualNetworks 'myVNET' provisioning status is Running
    - Resource Microsoft.Network/publicIPAddresses 'myPublicIP' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Storage/storageAccounts 'chefazurech05' provisioning status is Running
    - Resource Microsoft.Compute/virtualMachines 'ch05vm' provisioning status is Running
    - Resource Microsoft.Compute/virtualMachines 'ch05vm' provisioning status is Running
    - Resource Microsoft.Compute/virtualMachines 'ch05vm' provisioning status is Running
    - Resource Microsoft.Compute/virtualMachines 'ch05vm' provisioning status is Running
    - Resource Microsoft.Compute/virtualMachines 'ch05vm' provisioning status is Running
    - Resource Microsoft.Compute/virtualMachines 'ch05vm' provisioning status is Running
    - Resource Template deployment reached end state of 'Succeeded'.
    - deploy or re-deploy Resource Manager template 'chefazure-ch05-vm-deployment'
[2015-11-22T07:59:37+00:00] WARN: Skipping final node save because override_runlist was
given


Running handlers:
Running handlers complete
Chef Client finished, 2/2 resources updated in 6 minutes 13 seconds

Verifying WinRM Status

We can verify the status by using the Test-NetConnection cmdlet, which checks whether the specified port is open. When configured securely, WinRM runs on port 5986.

PS C:UsersStuartPrestonchefazure-ch05> Test-NetConnection chefazure-ch05-vm.westeurope.cloudapp.azure.com -Port 5986            
WARNING: Ping to chefazure-ch05-vm.westeurope.cloudapp.azure.com failed -- Status: TimedOut
ComputerName           : chefazure-ch05-vm.westeurope.cloudapp.azure.com
RemoteAddress          : 23.97.185.157
RemotePort             : 5986
InterfaceAlias         : Ethernet 2
SourceAddress          : 192.168.1.13
PingSucceeded          : False
PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded       : True

We can also RDP to the machine to verify that the WinRM server is configured correctly by typing the command winrm get winrm/config/service at an administrative command prompt as shown in Figure 5-1:

A346707_1_En_5_Fig1_HTML.jpg
Figure 5-1. WinRM configuration on a Windows 2012 R2 server

That covers the server side, but what about the client side? First of all, depending on your operating system, you may find that the WinRM service is not started, so let’s start it. At an administrative PowerShell session, type Start-Service winrm:

PS C:UsersStuartPrestonchefazure-ch05> Start-Service winrm            

As the certificate we uploaded wasn’t signed by a trusted certificate authority, and neither does the name of the certificate match the hostname we are connecting to, we need to specify a couple of options so that we can skip this checking when WinRM connects:

PS C:UsersStuartPrestonchefazure-ch05> $PSSessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck            
PS C:UsersStuartPrestonchefazure-ch05> Enter-PSSession -UseSSL -ComputerName chefazure-ch05-vm.westeurope.cloudapp.azure.com -Credential ch05vmazure -SessionOption $PSSessionOptions

After entering the password, you will be presented with a remote session:

[chefazure-ch05-vm.westeurope.cloudapp.azure.com]: PS C:UsersazureDocuments> hostname            
ch05vm
Note

Don’t forget to destroy your Resource Group after each exercise in the book!

We have now used Chef Provisioning and Azure to provision and Key Vault and used it to securely enable WinRM. Let’s have a look at the other types of resources that can be created in Azure.

Creating Other PaaS Resources via Chef Provisioning and Resource Explorer

Let’s imagine we wanted to use a brand new Azure PaaS resource that has just been announced publicly at a conference. We could wait until the API gets documented. Or we could use a tool called Resource Explorer to inspect and explore resources that have already been created in your subscription.

Let’s have a look at the process needed to automate the creation of any resource you can create through the Management Portal today.

Azure Data Factory describes itself as “a fully managed service for composing data storage, processing, and movement services into streamlined, scalable, and reliable data production pipelines.” (see https://azure.microsoft.com/en-us/documentation/videos/azure-data-factory-overview for more detail).

Immediately I can imagine hundreds of use cases for such a PaaS service as part of a larger architecture, so it sounds like a good candidate for our lesson in provisioning generic resources from Chef Provisioning. Azure has some useful tools for quickly creating provisioning templates for anything available in the gallery, if you know where to look. We’ll start from scratch, assuming no prior knowledge of this resource and work through the process for rapidly creating an Azure Resource Manager template and Chef Provisioning recipe.

Creating a Dummy Resource

The first thing I do when I want to have a play with new resources in Azure is to head to the Azure Management Portal ( https://portal.azure.com ). Every time I click New, I am overwhelmed with a list of new shiny things I can play with!

An example of this can be seen in Figure 5-2. The analytics space is clearly a fast-moving area with plenty of solutions already integrated into Azure and no doubt more to come. Let’s create a Data Factory.

A346707_1_En_5_Fig2_HTML.jpg
Figure 5-2. Example services that can be deployed from the Azure Marketplace

When we create a resource using the portal in Azure, there is a predictable process with a pattern to it. We select our resource, pick a subscription and resource group, add some settings/properties, and then press Create. Moments later we have a provisioned resource. Taking Data Factory as an example, Figure 5-3 shows the options that need to be supplied to provision a Data Factory.

A346707_1_En_5_Fig3_HTML.jpg
Figure 5-3. New Data Factory options

We simply need to supply the name, a Resource Group, and a Region to provision the resource. Once the resource has been provisioned we can view the Data Factory blade, as shown in Figure 5-4.

A346707_1_En_5_Fig4_HTML.jpg
Figure 5-4. Viewing our template data factory that has been provisioned

Viewing the Resource in Resource Explorer

Now the resource has been provisioned we can use the Resource Explorer tool , which can be found from the Portal by pressing Browse, then navigating to Resource Explorer (as shown in Figure 5-5).

A346707_1_En_5_Fig5_HTML.jpg
Figure 5-5. Finding the Resource Explorer in the Browse list in the Azure Management Portal

You are presented here with a tree-view containing all the Providers and importantly, Subscriptions available. Drilling into the Subscription we can see a list of all the available Resource Groups, from which we can further expand and see the Resources that have been provisioned in that Resource Group.

This can be seen in Figure 5-6, where we have expanded the chefazure-ch05 Resource Group to find a Resource of type Microsoft.DataFactory/dataFactories called template-datafactory.

A346707_1_En_5_Fig6_HTML.jpg
Figure 5-6. Resource Explorer showing the tempate-datafactory resource

Extracting the Template

As before, we need an ARM template and a Recipe so that we can use Chef Provisioning to provision our Data Factory.

Let’s start with the Resource definition found in Resource Explorer, by navigating to Deployments/Microsoft.DataFactory-template-datafactory. In the output window (as shown in Figure 5-7), we can see a templateLink element, in our case the URL: https://gallery.azure.com/artifact/20151001/Microsoft.DataFactory.0.9.3-preview/DeploymentTemplates/DataFactory.json and we can use this to retrieve the complete template used, and the parameters.

A346707_1_En_5_Fig7_HTML.jpg
Figure 5-7. Viewing the deployment JSON within Resource Explorer

Now we can proceed to create the required files for our deployment; we’ll need to store the deploy.json and a data_factory.rb in our cookbook as follows:

cookbooks/chefazure-ch05/files/data_factory/deploy.json:                  
{
    "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "name": {
            "type": "string"
        },
        "location": {
            "type": "string"
        },
        "apiVersion": {
            "type": "string",
            "defaultValue": "2015-01-01-preview"
        }
    },
    "resources": [
        {
            "apiVersion": "[parameters('apiVersion')]",
            "name": "[parameters('name')]",
            "location": "[parameters('location')]",
            "type": "Microsoft.DataFactory/dataFactories",
            "properties": {}
        }
    ]
}
cookbooks/chefazure-ch05/recipes/data_factory.rb:
require 'chef/provisioning/azurerm'
with_driver 'AzureRM:b6e7eee9-YOUR-GUID-HERE-03ab624df016'


azure_resource_group 'chefazure-ch05-ne' do
  location 'North Europe'
  tags CreatedFor: 'Using Chef with Microsoft Azure book'
end


azure_resource_template 'chefazure-ch05-datafactory-deployment' do
  resource_group 'chefazure-ch05-ne'
  template_source 'cookbooks/chefazure-ch05/files/data_factory/deploy.json'
  parameters name: 'chefazure-ch05-datafactory',
             location: 'North Europe'
end

Running a Custom Deployment

Now that we have retrieved our Resource Manager template, stored it in our cookbook, and created a recipe that uses it, we can execute it using the Chef Client as follows:

PS C:UsersStuartPrestonchefazure-ch05> knife cookbook upload chefazure-ch05              
Uploading chefazure-ch05 [0.1.0]
Uploaded 1 cookbook.
PS C:UsersStuartPrestonchefazure-ch05> chef-client -o recipe[chefazure-ch05::data_factory]
Starting Chef Client, version 12.5.1
[2015-11-22T17:16:51+00:00] WARN: Run List override has been provided.
[2015-11-22T17:16:51+00:00] WARN: Original Run List: []
[2015-11-22T17:16:51+00:00] WARN: Overridden Run List: [recipe[chefazure-ch05::data_factory]]
resolving cookbooks for run list: ["chefazure-ch05::data_factory"]
Synchronizing Cookbooks:
  - chefazure-ch05 (0.1.0)
Compiling Cookbooks...
Converging 2 resources
Recipe: chefazure-ch05::data_factory
  * azure_resource_group[chefazure-ch05-ne] action create
    - create or update Resource Group chefazure-ch05-ne
  * azure_resource_template[chefazure-ch05-datafactory-deployment] action deploy
    - Result: Accepted
    - Resource Microsoft.DataFactory/dataFactories 'chefazure-ch05-datafactory' provisioning Status is Running
    - Resource Microsoft.DataFactory/dataFactories 'chefazure-ch05-datafactory' provisioning Status is Running
    - Resource Template deployment reached end state of 'Succeeded'.
    - deploy or re-deploy Resource Manager template 'chefazure-ch05-datafactory-deployment'
[2015-11-22T17:17:27+00:00] WARN: Skipping final node save because override_runlist was given


Running handlers:
Running handlers complete
Chef Client finished, 2/2 resources updated in 01 minutes 16 seconds

Having converged, we can now view that our Resource was provisioned correctly, as seen in Figure 5-8.

A346707_1_En_5_Fig8_HTML.jpg
Figure 5-8. A Data Factory created via Chef Provisioning and Azure Resource Manager

This was a very simple example, but we can now combine PaaS and IaaS resources when using Chef Provisioning and Azure, and this opens up many possibilities for the creation of hybrid environments.

Summary

In this chapter we explored Azure Resource Manager and demonstrated how to use it with all the features of Chef Provisioning. As we have been able to see - integrating Chef with Azure is not just about provisioning compute (VM) resources, and this enables Chef to be used to provision hybrid PaaS and IaaS environments in a reliable, repeatable way.

The number of services in Azure grows weekly, a pace far faster than this book can keep up with. So we demonstrated how to interpret Resource Explorer and create your own templates as and when new services become available.

In the next chapter we’re going to take a look how we can use Azure as part of our quest for quality when using the Chef toolset.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.249.174