Skip to content

Instantly share code, notes, and snippets.

@tillig
Created November 5, 2020 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tillig/b410d2fe173c5080cd983716a58756e2 to your computer and use it in GitHub Desktop.
Save tillig/b410d2fe173c5080cd983716a58756e2 to your computer and use it in GitHub Desktop.
Creates a new Azure Container Registry with customer managed keys enabled.
<#
.SYNOPSIS
Creates a new Azure Container Registry.
.DESCRIPTION
Creates a new Azure Container Registry instance with customer managed key
encryption enabled. Generates keys and identities for the registry.
The location of the registry is inferred from the location of the specified
Key Vault. This is because, when using customer managed keys, the Key Vault
and the resource being encrypted must be in the same geographic location.
This is a limitation of the Azure platform.
.PARAMETER Name
The name of the container registry to create. This name must be globally
unique (across all Azure), must be alphanumeric characters only, and between
5 and 50 characters long. The managed identity name will be based on the ACR
name provided here with the suffix `-acr`. The key created for the registry
will be `cmk-acr-name`.
.PARAMETER KeyVault
The name of the Azure Key Vault in which the customer managed key should be
created. The managed identity for the ACR will get key permissions in this
vault to allow the ACR to use the generated key.
.PARAMETER ResourceGroup
The name of the resource group in which the ACR and associated identity
should be created. The Key Vault does not need to be in this resource group.
It is assumed the group exists already; it will not be created.
.PARAMETER Subscription
The subscription in which the ACR will be created. The resource group and
Key Vault also need to be in this subscription.
.EXAMPLE
./New-AzureContainerRegistry.ps1 `
-Name "myregistry" `
-Subscription "34d0efec-3fa8-4abe-a5e5-1d46d93183ec" `
-KeyVault "myvault" `
-ResourceGroup "mygroup"
Creates a managed identity "myregistry-acr" in the "mygroup" resource group
and grants it access to the "myvault" Azure Key Vault so it can be used to
handle CMK for the registry.
Creates a new key in the "myvault" Azure Key Vault called "cmk-acr-myregistry"
which will be used for encrypting the registry.
Creates an Azure Container Registry called "myregistry" in the "mygroup"
resource group and in the same geographic location as the "myvault" Azure Key
Vault. Enables CMK via the managed identity and key that were created.
#>
[CmdletBinding(SupportsShouldProcess = $True)]
Param(
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[ValidateLength(5, 50)]
[ValidatePattern('[a-z0-9]+')]
[string]
$Name,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[string]
$KeyVault,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[string]
$ResourceGroup,
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[Guid]
$Subscription
)
Begin {
Write-Verbose "Checking for az CLI."
If ($null -eq (Get-Command "az")) {
Throw "The az CLI was not found. Install here: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli"
Exit 1
}
}
Process {
# Begin happens once per script execution; Process happens for every item in
# the pipeline if it's executed as part of a pipeline run.
function Retry {
param (
[Parameter(Mandatory = $True)]
[int]
$Retries,
[Parameter(Mandatory = $True)]
[int]
$SecondsBetweenRetries,
[Parameter(Mandatory = $True)]
[ScriptBlock]
$ScriptBlock
)
$TryCount = 0
Do {
$TryCount++
Try {
$ScriptBlock.Invoke()
If ($LASTEXITCODE -ne 0) {
throw "Error executing program in script block."
}
Return
}
Catch {
$RetryError = $_
Write-Verbose "Failed on try $TryCount / $Retries - sleeping before retry."
Start-Sleep -Seconds $SecondsBetweenRetries
}
} While ($TryCount -le $Retries);
throw $RetryError
}
# Progress is managed at two levels:
# - Overall progress
# - Pre-flight check vs. deployment
# It's assumed the pre-flight check section is about 50% of the progress
# and the deployment is the other 50%. Progress increments are just
# "divide 100 by the number of steps in the section." It's not complex.
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Pre-Flight Check" -CurrentOperation "Verifying access to subscription $Subscription" -PercentComplete 0
&az account show -s $Subscription | Out-Null
If ($LASTEXITCODE -ne 0) {
Throw "Unable to access subscription $Subscription. Verify you have done 'az login' and have access to the subscription."
Exit 1
}
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Pre-Flight Check" -CurrentOperation "Verifying presence of resource group $ResourceGroup" -PercentComplete 8
&az group show -g $ResourceGroup --subscription $Subscription | Out-Null
If ($LASTEXITCODE -ne 0) {
Throw "Unable to locate resource group $ResourceGroup. The resource group must exist prior to creating the ACR."
Exit 1
}
# CMK requires the Key Vault to be in the same geographic region as the
# thing(s) consuming keys. Get location while checking for presence.
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Pre-Flight Check" -CurrentOperation "Verifying presence and location of Azure Key Vault $KeyVault" -PercentComplete 16
$KeyVaultJson = &az keyvault show -n $KeyVault --subscription $Subscription
If ($LASTEXITCODE -ne 0) {
Throw "Unable to locate Azure Key Vault $KeyVault. The Key Vault must exist prior to creating the ACR."
Exit 1
}
$KeyVaultData = $KeyVaultJson | ConvertFrom-Json
$KeyVaultLocation = $KeyVaultData.location
Write-Verbose "Key Vault $KeyVault is in $KeyVaultLocation. The ACR will also be in this location."
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Pre-Flight Check" -CurrentOperation "Verifying ACR $Name does not already exist" -PercentComplete 24
$ExistingAcrCount = az acr list --subscription $Subscription --query "length([?name=='$Name'])"
If ($ExistingAcrCount -ne 0) {
Throw "Found an existing ACR with the name $Name. The registry must not already exist."
Exit 1
}
$IdentityName = "$Name-acr"
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Pre-Flight Check" -CurrentOperation "Verifying managed identity $IdentityName does not already exist" -PercentComplete 32
$ExistingIdentityCount = az identity list --subscription $Subscription -g $ResourceGroup --query "length([?name=='$IdentityName'])"
If ($ExistingIdentityCount -ne 0) {
Throw "Found an existing identity with the name $IdentityName. The managed identity must not already exist."
Exit 1
}
$KeyName = "cmk-acr-$Name"
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Pre-Flight Check" -CurrentOperation "Verifying key $KeyName in vault $KeyVault does not already exist" -PercentComplete 40
$ExistingKeyCount = az keyvault key list --vault-name $KeyVault --subscription $Subscription --query "length([?name=='$KeyName'])"
If ($ExistingKeyCount -ne 0) {
Throw "Found an existing key with the name $KeyName in vault $KeyVault. The key must not already exist."
Exit 1
}
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Deployment" -CurrentOperation "Create managed identity for ACR" -PercentComplete 50
If ($PSCmdlet.ShouldProcess($IdentityName, "Create managed identity for ACR")) {
$IdentityJson = az identity create `
--resource-group $ResourceGroup `
--name $IdentityName `
--location $KeyVaultLocation `
--subscription $Subscription
If ($LASTEXITCODE -ne 0) {
Exit 1
}
$IdentityData = $IdentityJson | ConvertFrom-Json
$IdentityId = $IdentityData.id
$IdentityPrincipal = $IdentityData.principalId
Write-Verbose "Created identity $IdentityName. Object ID: $IdentityId; Principal ID: $IdentityPrincipal"
# Wait for the identity to be available. Eventual consistency means this could be a while.
Retry -Retries 30 -SecondsBetweenRetries 5 -ScriptBlock {
Write-Verbose "Waiting for eventual consistency to propagate identity $IdentityName..."
$ExistingIdentityCount = az identity list --subscription $Subscription -g $ResourceGroup --query "length([?name=='$IdentityName'])"
If ($ExistingIdentityCount -eq 0) {
Throw "Identity $IdentityName not found."
}
}
}
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Deployment" -CurrentOperation "Grant Key Vault access to managed identity" -PercentComplete 65
If ($PSCmdlet.ShouldProcess($KeyVault, "Grant key access to managed identity")) {
az keyvault set-policy `
--resource-group $ResourceGroup `
--name $KeyVault `
--object-id $IdentityPrincipal `
--subscription $Subscription `
--key-permissions get unwrapKey wrapKey | Out-Null
If ($LASTEXITCODE -ne 0) {
Exit 1
}
}
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Deployment" -CurrentOperation "Create customer managed key" -PercentComplete 70
If ($PSCmdlet.ShouldProcess($KeyVault, "Create customer managed key")) {
$KeyJson = az keyvault key create `
--name $KeyName `
--subscription $Subscription `
--vault-name $KeyVault
If ($LASTEXITCODE -ne 0) {
Exit 1
}
$KeyData = $KeyJson | ConvertFrom-Json
$KeyId = $KeyData.key.kid
Write-Verbose "Created key $KeyName. Key ID: $KeyId"
}
Write-Progress -Activity "Provisioning Azure Container Registry" -Status "Deployment" -CurrentOperation "Create Azure Container Registry" -PercentComplete 85
If ($PSCmdlet.ShouldProcess($Name, "Create Azure Container Registry")) {
az acr create `
--resource-group $ResourceGroup `
--name $Name `
--identity $IdentityId `
--key-encryption-key $KeyId `
--location $KeyVaultLocation `
--subscription $Subscription `
--sku Premium
If ($LASTEXITCODE -ne 0) {
Exit 1
}
}
Write-Progress -Activity "Provisioning Azure Container Registry" -Completed
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment