If you’re using Azure CDN and need any form of CI/CD, you will be mucking around with custom certs.

There’s currently no documented way to apply custom certs to custom domains. The below is how you acheive that (with a cert. stored in keyvault):

URI:

 "https://management.azure.com/subscriptions/$($subId)/resourcegroups/$($resourceGroupCdn)/providers/Microsoft.Cdn/profiles/$($cdnProfileName)/endpoints/$($cdnEndpointName)/customdomains/$(customDomain)/enableCustomHttps?api-version=2018-04-02" 

Method: POST

Body:

  
{
    
"certificateSource":"AzureKeyVault",
    
"protocolType":"ServerNameIndication",
    
"certificateSourceParameters":
    
{
      
"@odata.type":"#Microsoft.Azure.Cdn.Models.KeyVaultCertificateSourceParameters",
      
"subscriptionId":"$subId",
      
"resourceGroupName":"$resourceGroupKv",
      
"vaultName":"$kvName",
      
"SecretName":"$secretName",
      
"SecretVersion":"$version",
      
"updateRule":"NoAction",
      
"deleteRule":"NoAction"
    
}
  
}' 

OR! Don’t go alone, take this:

  
<# .SYNOPSIS Script to verify/validate a DNS record, and remediate if required/desired .DESCRIPTION This script checks the input DNS record, and either outputs the current configuration, or adds/sets the DNS record if it doesn't exist .PARAMETER servicePrincipalId The service principal to use to auth. to Azure .PARAMETER servicePrincipalSecret The secret of the service principal .PARAMETER tenantId ID of the Azure AAD tenant e.g. "1aaaea9d-df3e-4ce7-a55d-43de56e79442" .PARAMETER subscriptionName Subscription where our resources reside .PARAMETER resourceGroupCdn The resource group where the CDN resides; not necissarily the same place as other resources .PARAMETER resourceGroupKV The resource group where the KV instance redices; definitely not the same place as other resources .PARAMETER cdnProfileName The profile name for the CDN instance .PARAMETER cdnEndpointName The endpoint name for the specific proxy solution we're configuring; is constructed in the ARM template. .PARAMETER kvName The name of the keyvault instance where our super secret stuff lives. .PARAMETER secretName The secret in question, in this instance is a certificate .INPUTS none .OUTPUTS nono .EXAMPLE .\Provision-AzureCdnCustomCert -servicePrincipalId "<id>" -servicePrincipalSecret "hunter2" -tenantId "<guid>" -subscriptionName "Sub name" -resourceGroupCdn "resourcegroup" -resourceGroupKv "kv-resource-group" -cdnProfileName "cdn-profile" -cdnEndpointName "console-preprod" -kvName "shareddev-vault" -secretName "certName"

  
#>


param(
      
[CmdletBinding()]
      
[Parameter(Position=0, mandatory=$true)]
      
[string]$servicePrincipalId,

[Parameter(Position=1, mandatory=$true)]
      
[string]$servicePrincipalSecret,

[Parameter(Position=2, mandatory=$true)]
      
[string]$tenantId,

[Parameter(Position=3, mandatory=$true)]
      
[string]$SubscriptionName,

[Parameter(Position=4, mandatory=$true)]
      
[string]$resourceGroupCdn,

[Parameter(Position=5, mandatory=$true)]
      
[string]$resourceGroupKV,

[Parameter(Position=6, mandatory=$true)]
      
[string]$cdnProfileName,

[Parameter(Position=7, mandatory=$true)]
      
[string]$cdnEndpointName,

[Parameter(Position=8, mandatory=$true)]
      
[string]$kvName,

[Parameter(Position=9, mandatory=$true)]
      
[string]$secretName
  
)

Set-Location $PSScriptRoot

\# Function we use to get access token

  
if ($env:AGENT_RELEASEDIRECTORY -eq $null)
  
{
      
Import-Module .\Azure-AccountLibrary.psm1
  
} else
  
{
      
Import-Module "$($env:AGENT\_RELEASEDIRECTORY)\\_ARTIFACT_SCRIPTS\artifactScripts\citools\Azure-AccountLibrary.psm1"
  
}

\# Required for keyvault access

  
Login-Azure -ServicePrincipalId $servicePrincipalId -ServicePrincipalSecret $servicePrincipalSecret -SubscriptionName $SubscriptionName -Tenant $tenantId

$subId = (Get-AzureRmSubscription -SubscriptionName $SubscriptionName | ? {$_.State -eq "Enabled"}).Id

\# Get the keyvault secret version, keyvault RG, name and secret name are specificed

  
$version = (Get-AzureRmKeyVault -ResourceGroupName $resourceGroupKV -VaultName $kvName | Get-AzureKeyVaultSecret -Name $secretName).Version

$customDomain = $cdnEndpointName.Split("-")[1] + ".domain.io"

$StateUri = "https://management.azure.com/subscriptions/$($subId)/resourcegroups/$($resourceGroupCdn)/providers/Microsoft.Cdn/profiles/$($cdnProfileName)/endpoints/$($cdnEndpointName)/customdomains/customDomain?api-version=2018-04-02"
  
$ProvisionUri = "https://management.azure.com/subscriptions/$($subId)/resourcegroups/$($resourceGroupCdn)/providers/Microsoft.Cdn/profiles/$($cdnProfileName)/endpoints/$($cdnEndpointName)/customdomains/$($customDomain)/enableCustomHttps?api-version=2018-04-02"

\# Assign to variables, fill out body

  
$body = $ExecutionContext.InvokeCommand.ExpandString('{"certificateSource":"AzureKeyVault","protocolType":"ServerNameIndication","certificateSourceParameters":{"@odata.type":"#Microsoft.Azure.Cdn.Models.KeyVaultCertificateSourceParameters","subscriptionId":"$subId","resourceGroupName":"$resourceGroupKv","vaultName":"$kvName","SecretName":"$secretName","SecretVersion":"$version","updateRule":"NoAction","deleteRule":"NoAction"}}')

$token = Get-AzureRmCachedAccessToken

$headers = @{}
  
$headers.Add('Authorization',"Bearer $token")
  
$headers.Add('Content-Type','application/json')

write-verbose -verbose "[INF] Uri: $($uri)"
  
write-verbose -verbose "[INF] Body: $($body)"

\# Purge the CDN

  
$purgeUrl = "https://management.azure.com/subscriptions/$($subId)/resourcegroups/$($resourceGroupCdn)/providers/Microsoft.Cdn/profiles/$($cdnProfileName)/endpoints/$($cdnEndpointName)/purge?api-version=2018-04-02"
  
$purgeBody = '{"contentPaths": ["/*"]}'

Write-Verbose -Verbose "[INF] Purging CDN content"
  
Invoke-RestMethod -UseBasicParsing -Uri $purgeUrl -Headers $headers -Body $purgeBody -Method POST

\# Check the provisioning state

  
$state = (Invoke-RestMethod -Method GET -UseBasicParsing -Uri $StateUri -Headers $headers).properties.customHttpsProvisioningState

if ($state -eq "Enabling"){
      
\# In progress, exit gracefully.

      
Write-Verbose -Verbose "[INF] Current state is $($state), so exiting"
  
}
  
else
  
{
      
\# Need to enable

      
Write-Verbose -Verbose "Applying custom cert to $($cdnProfileName):$($cdnEndpointName)"
      
Invoke-RestMethod -Method Post -UseBasicParsing -Uri $ProvisionUri -Headers $headers -Body $body
  
}

Auth module below:

(You will have to edit certain things for your environment)

  
function Get-AzureRmCachedAccessToken()
  
{
    
$ErrorActionPreference = 'Stop'

if(-not (Get-Module AzureRm.Profile)) {
      
Import-Module AzureRm.Profile
    
}
    
$azureRmProfileModuleVersion = (Get-Module AzureRm.Profile).Version
    
\# refactoring performed in AzureRm.Profile v3.0 or later

    
if($azureRmProfileModuleVersion.Major -ge 3) {
      
$azureRmProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
      
if(-not $azureRmProfile.Accounts.Count) {
        
Write-Error "Ensure you have logged in before calling this function."
      
}
    
} else {
      
\# AzureRm.Profile < v3.0 $azureRmProfile = [Microsoft.WindowsAzure.Commands.Common.AzureRmProfileProvider]::Instance.Profile if(-not $azureRmProfile.Context.Account.Count) { Write-Error "Ensure you have logged in before calling this function." } } $currentAzureContext = Get-AzureRmContext $profileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient($azureRmProfile) Write-Debug ("Getting access token for tenant" + $currentAzureContext.Subscription.TenantId) $token = $profileClient.AcquireAccessToken($currentAzureContext.Subscription.TenantId) # Return $token.AccessToken } function Login-Azure { <# .SYNOPSIS Logs the context into a different subscription .DESCRIPTION This function logs the given context into a different subscription (context). We have to do this here, because all DNS resides in a different subscription. Because we like to make things easy. #>

      
[CmdletBinding()]
      
param(
      
[Parameter(Position=0, mandatory=$true)]
      
[string]$ServicePrincipalId,

[Parameter(Position=1, mandatory=$true)]
      
[string]$ServicePrincipalSecret,

[Parameter(Position=2, mandatory=$true)]
      
[string]$SubscriptionName,

[Parameter(Position=3, mandatory=$true)]
      
[string]$Tenant
      
)

Write-Verbose -Verbose "Logging into Azure.."
    
$secureStringPwd = ConvertTo-SecureString $servicePrincipalSecret -AsPlainText -Force
    
$creds = New-Object System.Management.Automation.PSCredential($servicePrincipalId,$secureStringPwd)
    
Login-AzureRmAccount -Credential $creds -ServicePrincipal -TenantId $tenant
    
Get-AzureRmSubscription -subscriptionName $SubscriptionName | Set-AzureRmContext
  
} 

Export-ModuleMember -function *
  

Note: Thanks to Sam for the typo heads-up!