Using Key Vault in Power Automate flow

Last week I wanted to use Key Vault from a Power Automate flow, but the Key Vault had firewall settings so it could not be reached. If you just want to know about using Key Vault from Power Automate, have a look at Azure Key Vault and Power Automate - keep your secrets safe., this is more about setting the firewall settings and about automating this.

The IP addresses Microsoft provides us with can be found at Managed connectors outbound IP addresses. It is unfortunate that Power Automate is not one of the trusted Microsoft services that you can enable. Then again turning this setting off gives you more control:

Key Vault networking setting

As it is possible that these IP addresses might change in the future, and the Azure Admins where not happy about manually adding/maintaining these IP addresses, one of them suggested that it might be possible to automate this. As I thought that might be an interesting challenge, last Sunday morning I created a script to do just that. I used the Azure CLI so you will need to have that installed. This script presumes you are already connected and that the subscription where the Key Vault resides is already set. You could make this part of the script of course.

az login --tenant [tenant name]
az account set --subscription "[guid of subscription]"

To parse the HTML I searched for a PowerShell module to do just that (as the old parsing is not available on PowerShell core). I found the module PowerHTML so installing this is a prerequisite for this script to run. The hardest part for me was figuring out the CIDR notation calculation that was needed, most part of the +/- 2.5 hours of creating the script was spend on that. I eventually created a quick and dirty method, that only takes the last part of the IP4 address into account, as I think that is sufficient.

Here is the script I created and that I tested on my tenant:

# uses module PowerHTML for parsing the html content

Import-Module -ErrorAction Stop PowerHTML

$ipAddressesSourceUrl = "https://docs.microsoft.com/en-us/connectors/common/outbound-ip-addresses"

Write-Host "Retrieving IP information from Microsoft site"
Write-Host "Url: $ipAddressesSourceUrl"
$response = Invoke-WebRequest -Uri $ipAddressesSourceUrl -Method Get

Write-Host "Scraping HTML for IP Addresses for Power Platform Europe" # Change if needed, or better create a variable fo region
$htmlDom = ConvertFrom-Html -Content $response.RawContent
$tables = $htmlDom.SelectNodes("//table")
# Second table is: Power Platform
$tdEurope = $tables[1].SelectNodes("//tr/td") | Where-Object { $_.InnerText -ieq "Europe" } # Update if you need a different region
$nextTd = $tdEurope.NextSibling
while ($nextTd -and $nextTd.NodeType -ne "Element" -and $nextTd.Name -ine "td") {
    $nextTd = $nextTd.NextSibling
}


function GetFromDifference($value) {
    if ($value -eq 127) { return 1 }
    if ($value -eq 63) { return 2 }
    if ($value -eq 31) { return 3 }
    if ($value -eq 15) { return 4 }
    if ($value -eq 7) { return 5 }
    if ($value -eq 3) { return 6 }
    if ($value -eq 1) { return 7 }
    if ($value -eq 0) { return 8 }
    throw "Invalid value for ip difference"
}

# quick way for determining cidr block by only looking at the last ip4 part
function GetIpWithCidrFromValue($value) {
    if ($value.Contains("-")) { # ip delimiter used in text
        $ips = $value -split '-' | ForEach-Object { $_.Trim() }
        $parts1 = $ips[0].Split(".")
        $parts2 = $ips[1].Split(".")
        if ($parts1.Length -ne 4 -or $parts2.Length -ne 4) {
            throw "Invalid IP Addresses"
        }
        $d = [int]$parts2[3] - [int]$parts1[3]
        $format = 24 + (GetFromDifference $d)
        return "$($ips[0])/$format"
    }
    else {
        if ($value.Split(".").Length -ne 4) {
            throw "Invalid IP Address"
        }
        return "$value/32"
    }
}

Write-Host "Getting CIDR values"
$ipRanges = @()
$ipValues = $nextTd.InnerText -split ',' | ForEach-Object { $_.Trim() }
$ipValues | ForEach-Object {
    $ipRanges += GetIpWithCidrFromValue $_
}

if ($ipRanges.Length -eq 0) {
    Write-Host "No IP addresses found."
    Exit
}

# add custom ip ranges
$ipRanges += "[xxx.xxx.xxx.xxx/xx]" # add one or more of your existing ip ranges here, so they will not be removed!

$keyVaultName = "[keyvaultname]" # update with the key vault name
$hasUpdates = $false

# Get current settings
Write-Host "Retrieving current settings for: $keyVaultName"
$rules = az keyvault network-rule list --name $keyVaultName | ConvertFrom-Json
$rules.ipRules | ForEach-Object { 
    $cidr = $_.value
    if (-not $ipRanges.Contains($cidr)) { 
        Write-Host "Not Found in source, so removing: $cidr" 
        az keyvault network-rule remove --name $keyVaultName --ip-address $cidr | Out-Null
        $hasUpdates = $true
    } 
}

$ipRanges | ForEach-Object {
    $cidr = $_
    $find = $rules.ipRules | Where-Object { $_.value -eq $cidr }
    if (-not $find) {
        Write-Host "Not Found in destination, so adding: $cidr" 
        az keyvault network-rule add --name $keyVaultName --ip-address $cidr | Out-Null
        $hasUpdates = $true
    }
}

if (-not $hasUpdates) {
    Write-Host "Nothing to update..."
}

Write-Host "Processing IP Addresses from Microsoft site finished."

So if you want to use this, add your own existing ip configuration to the $ipRanges, and set the [keyvaultname]. The script will scrape/parse the IP addresses from the html page, and you can update the region to something other than "Europe", please look at the table on the page for valid values. The script assumes the IP addresses for Power Automate will always be in the second table.

This script has been created so it is able to process updates if these take place. IP ranges that are not found in the source (on the page) will be removed, and IP addressed that are not found in the target will be added. So please make sure that existing IP ranges are added to the $ipRanges array.

A sample output:

Retrieving IP information from Microsoft site
Url: https://docs.microsoft.com/en-us/connectors/common/outbound-ip-addresses
Scraping HTML for IP Addresses for Power Platform Europe
Getting  values
Retrieving current settings for: *****************KeyVault
Not Found in destination, so adding: 40.89.131.3/32
Not Found in destination, so adding: 40.115.108.29/32
Not Found in destination, so adding: 52.174.180.160/32
Not Found in destination, so adding: 52.178.150.68/32
Not Found in destination, so adding: 94.245.91.93/32
Not Found in destination, so adding: 52.174.88.118/32
Not Found in destination, so adding: 40.91.208.65/32
Not Found in destination, so adding: 137.117.161.181/32
Not Found in destination, so adding: 13.69.171.0/32
Not Found in destination, so adding: 13.93.36.78/32
Not Found in destination, so adding: 20.82.226.52/32
Not Found in destination, so adding: 20.82.224.59/32
Not Found in destination, so adding: 20.126.243.151/32
Not Found in destination, so adding: 20.126.241.238/32
Not Found in destination, so adding: 20.103.132.139/32
Not Found in destination, so adding: 20.103.131.1/32
Not Found in destination, so adding: 13.69.227.208/28
Not Found in destination, so adding: 13.69.231.192/27
Not Found in destination, so adding: 13.69.64.208/28
Not Found in destination, so adding: 13.69.71.192/27
Not Found in destination, so adding: 20.82.246.112/28
Not Found in destination, so adding: 52.146.138.32/27
Not Found in destination, so adding: 20.86.93.32/27
Not Found in destination, so adding: 20.86.93.64/28
Processing IP Addresses from Microsoft site finished.

So now you can see, it isn't that hard to automate setting these IP addresses in the key vault firewall settings in Azure. And great to keep in mind, this can even be extended to other services/situations. Have fun with your PowerShell version of this.