diff --git a/scripts/powershell/Copy-AzKeyVaultSecret.ps1 b/scripts/powershell/Copy-AzKeyVaultSecret.ps1 index 15ccf15..402be59 100644 --- a/scripts/powershell/Copy-AzKeyVaultSecret.ps1 +++ b/scripts/powershell/Copy-AzKeyVaultSecret.ps1 @@ -6,23 +6,23 @@ - RBAC as Key Vault Contributor for Source and Destination Vault - Read access policy for secrets at source Key Vault and Write access policy for secrets at destination Key Vault - .PARAMETER VaultName - Specifies the name of the key vault. + .PARAMETER SourceVaultName + Specifies the name of the source key vault. .PARAMETER TargetVaultName Specifies the name of the target key vault. .PARAMETER SubscriptionId - Specifies the ID of Azure Subscription. + Specifies the ID of source Azure Subscription. .PARAMETER TargetSubscriptionId - Specifies the ID of Target Azure Subscription. + Specifies the ID of target Azure Subscription. - .PARAMETER Name - Specifies the name of the secret to copy. You can as well specify multiple Names for multiple secrets to copy. + .PARAMETER SecretName + Specifies the name of the secret to copy. You can as well specify multiple SecretNames for multiple secrets to copy. .PARAMETER SkipSecrets - Specifies the names of the secret skip copy operation. + Specifies the names of the secrets to skip during copy operation. .PARAMETER Force Forces the script to run without asking for user confirmation. @@ -33,37 +33,33 @@ .EXAMPLE This example shows how to copy all secrets from source to target vault. It requires manual user confirmation before executing copy operation for every secret separately: - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -Subscription $Subscription + .\Copy-AzKeyVaultSecret.ps1 -SourceVaultName -TargetVaultName -SubscriptionId .EXAMPLE Similar to example above, this shows how to copy all secrets from source to target vault. - But this time script does not require user confirmation as it uses -Force argument. - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -Subscription $Subscription -Force + But this time the script does not require user confirmation as it uses -Force argument. + .\Copy-AzKeyVaultSecret.ps1 -SourceVaultName -TargetVaultName -SubscriptionId -Force .EXAMPLE - This example shows how to use $Name Parameter to copy a single secret: - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -Subscription $Subscription -Name + This example shows how to use $SecretName Parameter to copy a single secret: + .\Copy-AzKeyVaultSecret.ps1 -SourceVaultName -TargetVaultName -SubscriptionId -SecretName .EXAMPLE - This example shows how to use $Name Parameter to copy multiple secrets: - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -Subscription $Subscription -Name , , + This example shows how to use $SecretName Parameter to copy multiple secrets: + .\Copy-AzKeyVaultSecret.ps1 -SourceVaultName -TargetVaultName -SubscriptionId -SecretName , , .EXAMPLE - This example shows how to copy all secrets from source to target vault without any confirmation: - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -Subscription $Subscription -Force + This example shows how to copy all secrets without confirmation when vaults reside in different Azure Subscriptions: + .\Copy-AzKeyVaultSecret.ps1 -SourceVaultName -TargetVaultName -SubscriptionId -TargetSubscriptionId -Force .EXAMPLE - This example shows how to copy all secrets without confirmation when vaults resides in different Azure Subscriptions: - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -SubscriptionId -TargetSubscriptionId -Force - - .EXAMPLE - This example shows same example as previous + use of SkipSecrets Parameter. SkipSecrets support multiple values: - .\Copy-AzKeyVaultSecret.ps1 -VaultName -TargetVaultName -SubscriptionId -TargetSubscriptionId -Force -SkipSecrets , , + This example shows the same example as previous + use of SkipSecrets Parameter. SkipSecrets supports multiple values: + .\Copy-AzKeyVaultSecret.ps1 -SourceVaultName -TargetVaultName -SubscriptionId -TargetSubscriptionId -Force -SkipSecrets , , #> param ( [Parameter(Mandatory = $true)] - [string]$VaultName, + [string]$SourceVaultName, [Parameter(Mandatory = $true)] [string]$TargetVaultName, @@ -75,7 +71,7 @@ param ( [string]$TargetSubscriptionId, [Parameter(Mandatory = $false)] - [string[]]$Name, + [string[]]$SecretName, [Parameter(Mandatory = $false)] [string[]]$SkipSecrets, @@ -89,12 +85,15 @@ param ( $InformationPreference = 'Continue' +# Log into Azure using a managed identity, for use in an Azure Automation Runbook. function aaLogin { Disable-AzContextAutosave -Scope Process $AzureContext = (Connect-AzAccount -Identity).context - $AzureContext = Set-AzContext -SubscriptionId $subscriptionId -DefaultProfile $AzureContext + Set-AzContext -SubscriptionId $SubscriptionId -DefaultProfile $AzureContext } -function Set-AzSubscription { + +# Set and verify Azure Subscription Context +function Select-SubscriptionContext { param ( [Parameter(Mandatory = $true)] @@ -102,9 +101,9 @@ function Set-AzSubscription { ) # Set Context to correct Subscription. - Set-AzContext -Subscription $SubscriptionId + Set-AzContext -SubscriptionId $SubscriptionId - # Check Subscription Context + # Verify Subscription Context $subCheck = (Get-AzContext).Subscription.Id try { if ($subCheck -ne $SubscriptionId) { @@ -113,9 +112,11 @@ function Set-AzSubscription { } } catch { - Write-Error "Error: $($_.Exception)" + Write-Error "Error: $($_.Exception.Message)" } } + +# Temporarily disable Key Vault firewall to allow script to read secrets in Source Vault and write secrets in Target Vault. function Disable-KeyVaultFirewall { param ( @@ -123,89 +124,105 @@ function Disable-KeyVaultFirewall { [string]$VaultName ) - $vault = Get-AzKeyVault -VaultName $VaultName -ErrorAction SilentlyContinue - $firewallDefaultAction = $vault.NetworkAcls.DefaultAction - $firewallEnabled = $firewallDefaultAction -eq 'Deny' - if ($firewallEnabled) { + $Vault = Get-AzKeyVault -VaultName $VaultName -ErrorAction SilentlyContinue + $FirewallDefaultAction = $Vault.NetworkAcls.DefaultAction + $FirewallEnabled = $FirewallDefaultAction -eq 'Deny' + + if ($FirewallEnabled) { $null = Update-AzKeyVaultNetworkRuleSet -VaultName $VaultName -DefaultAction 'Allow' } + + return $FirewallEnabled } + try { if ($Runbook) { aaLogin } - # Set AzContext and make sure FireWall is set to "Allowed" to be able to access secrets - Set-AzSubscription -SubscriptionId $SubscriptionId - Disable-KeyVaultFirewall -VaultName $VaultName - Disable-KeyVaultFirewall -VaultName $TargetVaultName - #Check if Source and Target Vaults exists - $srcVault = Get-AzKeyVault -VaultName $VaultName -ErrorAction SilentlyContinue - $dstVault = Get-AzKeyVault -VaultName $TargetVaultName -ErrorAction SilentlyContinue + # Set Context and make sure FireWall is set to "Allowed" to be able to access secrets + Select-SubscriptionContext -SubscriptionId $SubscriptionId + $SourceVaultFirewall = Disable-KeyVaultFirewall -VaultName $SourceVaultName + $TargetVaultFirewall = Disable-KeyVaultFirewall -VaultName $TargetVaultName + + # Check if Source and Target Vaults exist + $SourceVault = Get-AzKeyVault -VaultName $SourceVaultName -ErrorAction SilentlyContinue + $TargetVault = Get-AzKeyVault -VaultName $TargetVaultName -ErrorAction SilentlyContinue - # Skip Secrets form Copy operation if specified in $SkipSecrets Parameter - if ($SkipSecrets) { + if ($SourceVault -and $TargetVault) { + # Skip Secrets from Copy operation if specified in $SkipSecrets Parameter $exclusionList = @() - $exclusionList += $SkipSecrets - } - if ($srcVault -and $dstVault) { - # Run this block if no input in parameter $Name specified - if (!$Name) { + if ($SkipSecrets) { + $exclusionList += $SkipSecrets + } + + # Run this block if no input in parameter $SecretName specified + if (!$SecretName) { if ($SkipSecrets) { - $Name = (Get-AzKeyVaultSecret -VaultName $VaultName).Name | Where-Object { -not $exclusionList.Contains("$($_)") } | Sort-Object + $SecretName = (Get-AzKeyVaultSecret -VaultName $SourceVaultName).Name | Where-Object { -not $exclusionList.Contains($_) } | Sort-Object } + else { - $Name = (Get-AzKeyVaultSecret -VaultName $VaultName).Name | Sort-Object + $SecretName = (Get-AzKeyVaultSecret -VaultName $SourceVaultName).Name | Sort-Object } } - foreach ($n in $Name) { - # Get key vault secret - $srcSecretValue = Get-AzKeyVaultSecret -VaultName $VaultName -Name $n -AsPlainText -ErrorAction SilentlyContinue - $srcSecret = Get-AzKeyVaultSecret -VaultName $VaultName -Name $n -ErrorAction SilentlyContinue - # Set Destination Subscription Context if available + foreach ($n in $SecretName) { + # Get secret from source Key Vault + $SourceSecretValue = Get-AzKeyVaultSecret -VaultName $SourceVaultName -Name $n -AsPlainText -ErrorAction SilentlyContinue + $SourceSecret = Get-AzKeyVaultSecret -VaultName $SourceVaultName -Name $n -ErrorAction SilentlyContinue + + # Set Target Subscription Context if available if ($TargetSubscriptionId) { $null = Set-AzContext -SubscriptionId $TargetSubscriptionId } - # Compare Source and Destination Secret Values - <# Only copy if value of secret and existing destination do not match. - We need to convert secrets to plain text to be able to compare secret values in different keyvaults. - This is needed when copying/updating/backing_up secrets with a powershell script. - Default action of PowerShell command "Set-AzKeyVaultSecret" is creating new versions even when secrets have same SecretValue. - This is why we are comparing secrets, to be able to prevent new sercret versions with exact same secret values.#> - $dstSecretValue = Get-AzKeyVaultSecret -VaultName $TargetVaultName -Name $n -AsPlainText -ErrorAction SilentlyContinue - if ($srcSecretValue -ne $dstSecretValue) { - $SecretValue = $srcSecretValue | ConvertTo-SecureString -AsPlainText -Force + + # Get target secret from target Key Vault as plaintext for comparison + $TargetSecretValue = Get-AzKeyVaultSecret -VaultName $TargetVaultName -Name $n -AsPlainText -ErrorAction SilentlyContinue + + # Compare Source and Target Secret Values + if ($SourceSecretValue -ne $TargetSecretValue) { + $SecretValue = $SourceSecretValue | ConvertTo-SecureString -AsPlainText -Force $Copy = Set-AzKeyVaultSecret ` -VaultName $TargetVaultName ` -SecretValue $SecretValue ` - -Name $srcSecret.Name ` - -Expires $srcSecret.Expires ` - -ContentType $srcSecret.ContentType ` - -Tag $srcSecret.Tags ` + -Name $SourceSecret.Name ` + -Expires $SourceSecret.Expires ` + -ContentType $SourceSecret.ContentType ` + -Tag $SourceSecret.Tags ` -Confirm:(!$Force) + Write-Output "Successfully copied secret name '$($Copy.Name)' with id '$($Copy.Id)'" } + else { - Write-Output "Secret Name '$($srcSecret.Name)' with id '$($srcSecret.id)'already existing in destination vault" + Write-Output "Secret Name '$($SourceSecret.Name)' with id '$($SourceSecret.Id)' already exists in destination vault" } } } } + catch { - Write-Error "Error: $($_.Exception)" + Write-Error "Error: $($_.Exception.Message)" } + finally { # Void secrets - $srcSecretValue = "" - $dstSecretValue = "" + $SourceSecretValue = "" + $TargetSecretValue = "" $SecretValue = "" + # Switch Source Vault Firewall back to Deny if default action was 'Deny' - if ($srcFirewallEnabled) { - $null = Update-AzKeyVaultNetworkRuleSet -VaultName $VaultName -DefaultAction 'Deny' + if ($SourceVaultFirewall) { + $null = Update-AzKeyVaultNetworkRuleSet -VaultName $SourceVaultName -DefaultAction 'Deny' + + Write-Output "Firewall for Source Vault is set to $SourceVaultFirewall." } - # Switch Destination Vault Firewall back to Deny if default action was 'Deny' - if ($dstFirewallEnabled) { + + # Switch Target Vault Firewall back to Deny if default action was 'Deny' + if ($TargetVaultFirewall) { $null = Update-AzKeyVaultNetworkRuleSet -VaultName $TargetVaultName -DefaultAction 'Deny' + + Write-Output "Firewall for Target Vault is set to $SourceVaultFirewall." } }