Octopus Deploy: Automatically transform SSL thumbprint

Recently I had to enable secure connection (HTTPS) for my Azure Cloud Service web role that has three separate environments: educationtesting and production. I’m using Octopus Deploy to automate my deployments to Azure.

This guide will tell how you can automatically change the SSL certificate thumbprint in ServiceConfiguration.Cloud.cscfg based on the environment you are deploying to.

ServiceConfiguration.Cloud.cscfg

Below is a typical setup of a certificate in the cloud project’s service configuration (ServiceConfiguration.Cloud.cscfg) and I’m assuming you have it configured like this also.

...
<Role name="WebRoleName">  
  <Instances count="1" />
  <ConfigurationSettings>
      ...
  </ConfigurationSettings>

  <Certificates>
    <Certificate name="SslCertificate"
    thumbprint="SDA98FDS8ADSF978ADFS798SGA1221123421DSA"
    thumbprintAlgorithm="sha1" />
  </Certificates>
</Role>  
...

So, how to manipulate the certificate thumbprint attribute in Octopus deployment? With PowerShell of course.

Octopus pre-deployment script

The script below can be copy-pasted to your pre-deployment script field in your step template or process step. It will replace the values of certificate thumbprint attribute and (as a bonus) instance count for each role in your ServiceConfiguration.Cloud.cscfg.

You have to have variables in your Octopus Deploy project that are named exactly as said in the script description:
Azure.Role[rolename].Instances
Azure.Role[rolename].Certificate

Octopus Deploy: Variables

<#  
.SYNOPSIS
   Modifies some Azure cscfg entries that Octopus Deploy 2.0 is not able to as of today.
.DESCRIPTION
   Sets the thumbprint attribute's value of the certificate. Sets the instance count for each role. Octopus-variables must match either of the two forms:
   Azure.Role[rolename].Instances
   Azure.Role[rolename].Certificate
   where rolename is the roleName as defined in the ServiceConfiguration.Cloud.csfcg.
   The config file must be named ServiceConfiguration.Cloud.cscfg.
#>

$configFile = "ServiceConfiguration.Cloud.cscfg";
Write-host "Updating config file named" $configFile "for with instance and certificate values"  
Write-host ""

$OctopusVariablesRegex = @{     
    AzureRoleInstancesVariable = "Azure.Role\[(?<roleName>[^\]]+)\]\.Instances";
    AzureRoleCertificateVariable = "Azure.Role\[(?<roleName>[^\]]+)\]\.Certificate";
}

function GetCustomRoleInfoFromOctopusParameters(){  
    Write-host "Checking Octopus variables for the variables on format:"
    Write-host "* Azure.Role[roleName].Instances"
    Write-host "* Azure.Role[roleName].Certificate"
    Write-host ""

    $roleInstancesVariables = @{}
    $roleCertificatesVariables = @{}       

    foreach ($objItem in $OctopusParameters.Keys) {       

        $isInstancesVariableByName = $objItem -match $OctopusVariablesRegex.AzureRoleInstancesVariable

        if($isInstancesVariableByName){
            $roleName = $matches.roleName
            $roleInstancesVariables[$roleName] = $OctopusParameters[$objItem]           
        }


        $isCertificateVariable = $objItem -match $OctopusVariablesRegex.AzureRoleCertificateVariable
        if($isCertificateVariable){
            $roleName = $matches.roleName
            $roleCertificatesVariables[$roleName] = $OctopusParameters[$objItem]
        }
    }

    $roleInfo = @{ Instances = $roleInstancesVariables; Certificates = $roleCertificatesVariables}   
    Write-host "Found" $roleInstancesVariables.Keys.count "roles for instance update"
    Write-host "Found" $roleCertificatesVariables.Keys.count "roles for certificate update"    
    Write-host ""
    return $roleInfo
}

function ParseAzureConfigToXml(){

    if(!(test-path $configFile)){      
        WriteErrorMsg "Could not find ServiceConfig file named '$configFile'!"
        Break
    }

    [xml]$configFileAsXml = Get-Content $configFile
    return $configFileAsXml;
}

function UpdateFirstCertificateEntriesForEveryRole($xmlToBeUpdated, $certificatesForRoles) {

    foreach ($roleName in $certificatesForRoles.Keys) {
        $role = GetRole $xmlToBeUpdated $roleName    

        if($role.Certificates -eq $null){
            WriteErrorMsg "Role did not have defined Certificates section! Could not update first certificate value!"
            Break
        }       
        Write-host "Updating Certificate named" $role.Certificates.Certificate.name "for" $role.name
        $role.Certificates.Certificate.thumbprint = [string]$certificatesForRoles[$roleName];
    }
    return $xmlToBeUpdated
}

function UpdateInstanceCounts($xmlToBeUpdated, $instancesForRoles){

    foreach ($roleName in $instancesForRoles.Keys) {
        $role = GetRole $xmlToBeUpdated $roleName

        $newInstanceCount = $instancesForRoles[$roleName]
        $isInteger = IsIntegerValue $newInstanceCount
        if($isInteger -eq $false){
            WriteErrorMsg "Instance count update failed: Instance count for '$roleName' in Octopus variable is not an integer";
            Exit 1
        }
        Write-host "Updating instance count for role" $role.name "to" $instancesForRoles[$roleName]
        $role.Instances.count = $instancesForRoles[$roleName];
    }
    return $xmlToBeUpdated
}

function IsIntegerValue($val){  
    if($val -match "^[0-9]+$"){
        return $true
    }
    return $false
}

function GetRole($xml, $roleName){  
    $role = $xml.ServiceConfiguration.Role  | Where-Object {$_.name -eq $roleName }
    if($role -eq $null){          
        WriteErrorMsg "Could not find a role named '$roleName' in ServiceConfig";
        Break
    }
    return $role
}

function WriteErrormsg($msg){  
    Write-host "**** Could not update the ServiceConfiguration file! Error:." $msg
}

$roleInfo = GetCustomRoleInfoFromOctopusParameters
$xml = ParseAzureConfigToXml
$xmlWithNewCertificate = UpdateFirstCertificateEntriesForEveryRole $xml $roleInfo.Certificates
$xmlWithNewInstanceCounts = UpdateInstanceCounts $xmlWithNewCertificate $roleInfo.Instances
$xmlWithNewInstanceCounts.Save($configFile);
Write-host ""  
Write-host "ServiceConfiguration.Cloud.cscfg updated!"  

Octopus Deploy: Pre-deployment script

The script author is John Kors https://gist.github.com/johnkors/8847674. John’s script assumes that there are more than one certificates in your ServiceConfiguration.Cloud.cscfg. I modified the script to assume there is only one certificate installed.