Real World DevOps-Breaking It All Down (part 3-Setting up HTTPS)

Table of Content

Part 1 – Provisioning and Restoring DB’s from Backup

Part 2 – Setting up DNS

Part 3 – Setting up HTTPS

Part 4 – Settup up dynamic backend service url’s for mobile app (1 build for all stages) (Coming soon)

Setting up HTTPS

All modern websites today should have HTTPS enabled. And since I wanted my pipeline to do full disaster recovery, I knew I had to set up HTTPS. The first thing I needed was a certificate. Because my front end was an Azure App Service,I just got an Azure App Service Certificate. You don’t have to use an Azure App Service Certificate. Any certificate will work. What you need is the PFX file. But it was super simple for me to get an Azure App Service Certificate vs buying a cert somewhere else so that’s what I did.

Next, there was some configurations that needed to be done to the certificate. You need to store the cert in Key Vault, verify that you are the domain owner and finally, assign the certificate to your app.

image

Storing the certificate in Key Vault was simple enough. A few simple clicks from the portal saved my certificate into my key vault instance. Next, verifying I owned the domain name was simple too. The Azure Portal provided a set of instructions to add a TXT entry in DNS and voila, Verified. Finally, I needed to assign the certificate to my application. And since this would change every time a new instance of my app was provisioned, I wanted my pipeline to do it.

In my pipeline, I have a stage where I setup HTTPS for my web app. Since I stored my certificate in Key Vault, it is stored as a secret and it is stored as an encoded PFX file.

image

Retrieving it and storing it as a deployment variable with the same name as the key vault variable name is super simple using a Azure Key Vault task.

image

The Key Vault task does all this automatically. You just need to point it to the correct Key Vault.

After my Key Vault task got all my secrets (including my certificate), I called my provisionHttps.ps1 script. Nothing magical about this script. It’s a powershell script which uses the azure cli to set my certificate as the certificate for my azure app service.

Here is the parameter list for my script

[CmdletBinding()]
param(
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipal,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalSecret,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalTenantId,
    [Parameter(Mandatory = $True)]
    [string]
    $resourceGroupName,
    [Parameter(Mandatory = $True)]
    [string]
    $resourceGroupNameRegion,
    [Parameter(Mandatory = $True)]  
    [string]
    $webAppName,
    [Parameter(Mandatory = $True)]  
    [string]
    $dnsName,
    [Parameter(Mandatory = $True)]  
    [string]
    $pfx,
    [Parameter(Mandatory = $True)]  
    [string]
    $pfxPassword
)

And here are the parameters that I pass in from my pipeline

image

Nothing magical about these values except notice I pass in the webAppName, the dnsName, the pfx file and the pfxPassword.

  • webAppName – the name of the app service
  • dnsName – the dnsName I want to use. In my project, it is www.abelmercuryhealth-dev.com for the beta environment
  • pfx – the encoded pfx file. This is what is stored in Key Vault. Notice the name release variable abelmercuryhealthdev378b677f-5445-45a5-b253-f5e4e2b03379 matches the Key Vault variable name for the certificate
  • pfxPassword. This value is also stored in keyvault. You can set this to whatever you want.

After passing in the variables needed, the script logs into the azure using the azure cli

#region Login
# This logs in a service principal
#
Write-Output "Logging in to Azure with a service principal..."
az login `
    --service-principal `
    --username $servicePrincipal `
    --password $servicePrincipalSecret `
    --tenant $servicePrincipalTenantId
Write-Output "Done"
Write-Output ""

Next I decode the pfx file

    Write-Output "getting dev certificate..."
    $kvSecretBytes = [System.Convert]::FromBase64String($pfx)
    $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
    $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
    Write-Output "done getting dev certificate"
    Write-Output ""

I then save the decoded pfx file to disk with a password

    Write-Output "saving pfx to disk"
    $password = $pfxPassword
    $protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $password)
    $pfxPath = [Environment]::GetFolderPath("Desktop") + "\MyCert.pfx"
    [System.IO.File]::WriteAllBytes($pfxPath, $protectedCertificateBytes)
    Write-Output "done saving pfx to disk"
    Write-Output ""

Next I upload the certificate and set it to my web apps ssl and I get back the thumbprint

    Write-Output "uploading certificate, getting thumbprint"
    $thumbprint=$(az webapp config ssl upload `
    --name $webAppName `
    --resource-group $resourceGroupName `
    --certificate-file $pfxPath `
    --certificate-password $pfxPassword `
    --query thumbprint `
    --output tsv)
    Write-Output "done uploading certificate, thumbprint: $thumbprint"
    Write-Output ""

And finally I add the dns host name to my app service and bind the ssl certificate

    Write-Output " adding custom domain and adding certificate "
    az webapp config hostname add `
        --webapp-name $webAppName `
        --resource-group $resourceGroupName `
        --hostname $dnsName
    az webapp config ssl bind `
        --name $webAppName `
        --resource-group $resourceGroupName `
        --certificate-thumbprint $thumbprint `
        --ssl-type SNI

Here is the entire provisionHttps.ps1

# This IaC script provisions and configures the web stite hosted in azure
# storage, the back end function and the cosmos db
#
[CmdletBinding()]
param(
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipal,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalSecret,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalTenantId,
    [Parameter(Mandatory = $True)]
    [string]
    $resourceGroupName,
    [Parameter(Mandatory = $True)]
    [string]
    $resourceGroupNameRegion,
    [Parameter(Mandatory = $True)]  
    [string]
    $webAppName,
    [Parameter(Mandatory = $True)]  
    [string]
    $dnsName,
    [Parameter(Mandatory = $True)]  
    [string]
    $pfx,
    [Parameter(Mandatory = $True)]  
    [string]
    $pfxPassword
)
#region Login
# This logs in a service principal
#
Write-Output "Logging in to Azure with a service principal..."
az login `
    --service-principal `
    --username $servicePrincipal `
    --password $servicePrincipalSecret `
    --tenant $servicePrincipalTenantId
Write-Output "Done"
Write-Output ""
# this defines my time 1 up function which will configure https for my apps front door
#
function 1_Up {
    Write-Output "getting dev certificate..."
    $kvSecretBytes = [System.Convert]::FromBase64String($pfx)
    $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
    $certCollection.Import($kvSecretBytes,$null,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
    Write-Output "done getting dev certificate"
    Write-Output ""
    Write-Output "saving pfx to disk"
    $password = $pfxPassword
    $protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $password)
    $pfxPath = [Environment]::GetFolderPath("Desktop") + "\MyCert.pfx"
    [System.IO.File]::WriteAllBytes($pfxPath, $protectedCertificateBytes)
    Write-Output "done saving pfx to disk"
    Write-Output ""
    Write-Output "uploading certificate, getting thumbprint"
    $thumbprint=$(az webapp config ssl upload `
    --name $webAppName `
    --resource-group $resourceGroupName `
    --certificate-file $pfxPath `
    --certificate-password $pfxPassword `
    --query thumbprint `
    --output tsv)
    Write-Output "done uploading certificate, thumbprint: $thumbprint"
    Write-Output ""
    Write-Output " adding custom domain and adding certificate "
    az webapp config hostname add `
        --webapp-name $webAppName `
        --resource-group $resourceGroupName `
        --hostname $dnsName
    az webapp config ssl bind `
        --name $webAppName `
        --resource-group $resourceGroupName `
        --certificate-thumbprint $thumbprint `
        --ssl-type SNI
    Write-Output "Done with function 1_Up"
    Write-Output ""
}
Install-Module -Name VersionInfrastructure -Force -Scope CurrentUser
Update-InfrastructureVersion `
    -infraToolsFunctionName $Env:INFRATOOLS_FUNCTIONNAME `
    -infraToolsTableName $Env:INFRATOOLS_TABLENAME `
    -deploymentStage $Env:INFRATOOLS_DEPLOYMENTSTAGE
provisionHttps.ps1

Conclusion

Once again, before starting down this rabbit hole, I had no clue how I could set up HTTPS for a web app. Let alone, if it could be done from a pipeline. However, I’ve been down this path before. I was pretty sure I could do it using the Azure CLI which meant setting this up in a powershell script or a bash script would be simple, once I figured out what exactly needed to be done. Turns out, it wasn’t very hard. After some quick googling

  • I needed a PFX file of my certificate
  • I needed the password to the my decoded pfx file
  • az webapp config ssl upload – is the command to upload a certificate to the resource group of the web app.
  • az webapp config hostname add – is the command to add a custom domain to a web app
  • az webapp config ssl bind – binds the certificate to the web app

Googling some more, I realized that using an App Service Certificate stored in Key Vault was super easy. And when decrypting the certificate from Key Vault, I now had the encoded version of the pfx with no password. I can set it to whatever I needed. Once I figured out all of this, the script practically wrote itself.

  • Get the encoded pfx secret
  • Decode it and store it somewhere
  • Upload the certificate and get the thumbprint
  • add the custom domain to the app service
  • bind ssl certificate using the thumbprint

Voila! Done. Getting all configuration set up using IaC is totally doable. Whether using ARM templates, Terraform or even my roll my own IaC method using powershell + Azure CLI + my own versioning system.

Leave a Reply

Your email address will not be published. Required fields are marked *