Problem


I have Azure VMs that require being accessed only from 3 IP addresses. Two of these IPs are static, the other is dynamic. Every time the dynamic IP changes, I must manually go to Azure portal and update NSG rules. Because this happens every few days, it makes me work 996. To solve this issue, I created Azure Function to automatically update the NSG rule with a simple HTTP request. I no longer need to manually go into Azure portal. Let's see how to do it.

Create Azure Function App


PowerShell Function

Create a new Azure Function and select PowerShell as runtime. After creation is completed, go to Identity blade, and enable system assigned managed identity.

Add Az modules

Go to App files - requirements.psd1 and add PowerShell modules:

@{
    'Az.Accounts' = '2.*'
    'Az.Network' = '5.*'
}

Allow Function Access for NSG

Go to Access control (IAM) of the NSG, add a new role assignment

Select "Contributor" in "Privileged administrator roles"

Select "Managed identity" and add the function app.

Write Function Logic


HTTP Trigger

Create a new Function with HTTP Trigger

Logic code

Copy the code and replace variables to your own values. In my case, cnBridgeIp and corpIp are two static IP addresses. The dynamic IP will come from HTTP request query string or body. You can modify the code to your need.

using namespace System.Net

param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

$ip = $Request.Query.ip
if (-not $ip) {
    $ip = $Request.Body.Ip
}

$subscriptionId = "<id>"
$tenantId = "<id>"
$rsgName = "<group>"
$nsgName = "<nsg>"
$ruleName = "<rule>"
$cnBridgeIp = "<ip>"
$corpIp = "<ip>"

$body = "251"

if ($ip) {
    # Set-AzNetworkSecurityRuleConfig
    # https://learn.microsoft.com/en-us/powershell/module/az.network/set-aznetworksecurityruleconfig?view=azps-9.5.0

    Select-AzSubscription -SubscriptionID $subscriptionId -TenantID $tenantId
    $nsg = Get-AzNetworkSecurityGroup -Name $nsgName -ResourceGroupName $rsgName
    # $httpsRule = $nsg | Get-AzNetworkSecurityRuleConfig -Name $ruleName

    ($nsg.SecurityRules | Where-Object { $_.Name -eq $ruleName }).SourceAddressPrefix = ([System.String[]] @($cnBridgeIp, $corpIp, $ip))
    $nsg | Set-AzNetworkSecurityGroup | Get-AzNetworkSecurityRuleConfig -Name $ruleName

    $body = "Updated NSG $nsgName for IP: $ip."

    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
            StatusCode = [HttpStatusCode]::OK
            Body       = $body
        })
}
else {
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
            StatusCode = [HttpStatusCode]::NotFound
        })
}

Test

Click Test/Run, enter an IP address in HTTP request body.

 

Then go to your NSG to verify if the function logic is successful.

If everything is fine, click "Get function URL", then you can integrate this API endpoint to other part of your system to update NSG rule fully automatically for a dynamic IP address.

You may get your dynamic IP address by Azure Function or third-party APIs. For example, in a C# Function, you can do this:

// #r "System.Text.Json"

using System.Net;
// using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    // log.LogInformation("C# HTTP trigger function processed a request.");

    var clientIp = req.HttpContext.Connection.RemoteIpAddress.ToString();
    return new OkObjectResult(clientIp);
}