Faking DDNS with Azure Services
Published Jun 03 2021 12:00 AM 5,987 Views
Microsoft

Hello folks,

Since the beginning of the pandemic, we’ve all been mostly stuck to our home offices.  And since I’ve been concentrating of the hybrid services that Azure can provide I setup a simulated on-prem environment at home with left-over desktop, laptops, and Raspberry PI.

 

It’s been really cool and useful.  But now that vaccination is progressing and that the government is starting to talk about re-opening the economy and the border, I’m thinking I might get back on the road sooner rather than later.  I’ll want to access my “on-prem lab” environment from anywhere but my ISP cycles my IP address regularly.

 

I could always use a service like noip.com or dyndns but I already have a zone managed in Azure DNS and I thought it would be fun to figure it out.  Azure DNS doesn't currently support zone transfers so I could not setup a zone transfer from my own DNS server. DNS zones can be imported into Azure DNS by using the Azure CLI. DNS records are managed via the Azure DNS management portalREST APISDKPowerShell cmdlets, or the CLI tool.

 

dns1.png

Here is my V1 plan.

I already have a DNS server running on a Linux server in my on-prem environment.  I will schedule a job to get the external IP of my ISP’s router and send it to my zone in Azure.  Sounds easy….  Problem...  I don’t want to cache credentials for my job to connect to my zone and update the record.

 

Plan v1.1

Just like v1, I will schedule a job on my Linux server to get the external IP of my ISP’s router.  BUT!! I code it to send it to an Azure Function via a webhook and let the function update my record in the DNS zone in Azure.

 

That’s better...

 

I can secure the webhook with a function key and assign a managed identity to the function, so it only has rights to update the zone records but not change any of the other configuration and security.

 

That should work!

 

Let’s go.

Linux Server setup

I installed PowerShell Core on the Linux box since I’m more familiar with PowerShell than I am with bash.  But I could have used either.

Created a simple script   Some parts of the script like the Azure Function key and the Function webhook URL will be updated after we create the function.

 

 

 

 

Function Get-ExternalIP {
    $jsonContent = Invoke-WebRequest -UseBasicParsing -Uri "http://ipinfo.io/json" | ConvertFrom-Json

    $pattern = "^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$"

    If ($jsonContent.ip -match $pattern)
    {
        return $jsonContent.ip
    }
    Else
    {
        Write-Output "False"
        Return $false
    }
}

$logfilepath= "/home/wiredcanuck/pwsh.log"
Start-Transcript -Path $logfilepath

$datestamp = Get-Date
write-host $datestamp

# get the current DNS record (Using Dig since Resolve-DnsName in the DnsClient module as not yet been ported to PowerShell Core)
$EI = dig home.wiredcanuck.com +short
Write-host $EI.IPAddress

$IP = Get-ExternalIP

if ($IP -ne $EI)
{
    $WebHook = https://homednsupdate.azurewebsites.net/api/UpdateDNSRecord
# the code below id the Function Key
    $code = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    $name = "home"

    write-host "External IP is $IP"

    $URL = $WebHook+"?code="+$code+"&name="+$name+"&ip="+$IP

    write-host "calling WebHook"

    $response = Invoke-WebRequest -UseBasicParsing $URL -Method POST

    Write-host $response

}
Else
{
    write-host "Current IP ($IP) is the same as the IP in the DNS record ($EI).  no update needed"
}

 

 

 

 

Azure Function Setup

In the Azure portal I navigated to the Resource Group where my DNS Zone is lo0cation and created an Azure Function

 

dns4.png

 

I ensured to create the function with the PowerShell Core stack and using a consumption (Serverless) plan type.

 

dns5.png

Once the function was created, I navigated to the function App and clicked on the Functions menu item on the left of the screen.

 

dns6.png

And clicked the “+ Add” selection on the next screen and fill in the details like Using the Portal for development (1), “HTTP Trigger” from the template list (2), a function name (3) and “Function” as the authorization level.

 

dns7.png

Going back to the DNSUpdate1 function screen I made sure to navigate to the “Identity” section and turn on the system assigned identity. And save the changes and confirm on the next screen. That way the function has an identity we can use to restrict access.

 

dns8.png

When the changes are saved, you can click the Azure role assignments button to assign the proper rights.  In my case I only assigned “DNS Zone contributor” only in the specific resource group in my subscription.

 

dns9.png

Now that my function is created and has the proper rights, we can create the PowerShell code in the “Code + Test” section and insert our code.

 

dns10.png

 

The code will capture the HTTP request, and the body of the request and process the information.

 

 

 

 

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

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

# Interact with query parameters or the body of the request.

$RG = "WiredCanuck"
$ZoneName = "Wiredcanuck.com"

$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

$NewIP = $Request.Query.IP
if (-not $NewIP) {
    $IP = $Request.Body.IP
}

$RecordSet = Get-AzDnsRecordSet -ResourceGroupName $RG -ZoneName $ZoneName -Name $name -RecordType A

if (-not $RecordSet) {
    $RecordSet = New-AzDnsRecordSet -Name $name -RecordType A -ZoneName $ZoneName -ResourceGroupName $RG -Ttl 3600 -DnsRecords (New-AzDnsRecordConfig -IPv4Address $NewIP)
}
$OldIP = $recordset.Records.Ipv4Address

if ($NewIP -ne $OldIP) {
    $body = "DNS address $name.$ZoneName does not match. $NewIP will be created"
    $Records = @()
    $Records += New-AzDnsRecordConfig -IPv4Address $NewIP
    $RecordSet = New-AzDnsRecordSet -Name $name -RecordType A -ZoneName $ZoneName -ResourceGroupName $RG -Ttl 3600 -DnsRecords $Records -Confirm:$False -Overwrite
}
Else {
    $body = "Dynamic address ($NewIP) and DNS address ($OldIP) match.  No update needed"
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = [HttpStatusCode]::OK
    Body = $body
})

 

 

 

 

Once the code is saved, I can click the “Get function URL” to get the Webhook address for the on-prem portion of this solution.  The URL will include the Default function key as highlighted below

 

https://dnsupdate1.azurewebsites.net/api/HttpTrigger1?code=FPX43K329H7PEeJPl68ZC8LZfYCwkDyKJElShadzPWSxtc3LQASlbA==

 

dns11.png

 

And now, every 12 hours (as per my CRON job) the IP address of my home network will be checked and updated if need be.  You can monitor the results in the monitor section and see all the invocation of the function.

 

dns12.png

 

Conclusion

This just proves that even if some functionalities don’t exist yet, you can leverage multiple Azure services and configuration to address on-prem issues efficiently and securely.

 

Are you running into challenges that could be addressed with cloud services and a bit of imaginations?  Let me know.

 

Cheers!

 

Pierre

3 Comments
Brass Contributor

I use something quite similar but imho simpler : the machine/rpi/router calling every XX minutes (or even better on the dhcp hooks of the router) an azure function with these inputs : the zone name and the recordset to update (and the function key of course to protect it). The function itself gets the the client IP originating the request so I don't have any script running locally, just a curl call to the function in my crontab

Iron Contributor

Hi @Pierre Roman ,

thank you, this article is great!

 

Anyway, if I'm facing with frequent IP changes in the client side (the scenario is IoT with mobile Internet connection like 4G), could it be possible to configure more frequent updates?
Let's assume for instance that the IP is updated by the ISP every hour and I want to make sure that the update is managed every 10 minutes, I should change accordingly:

1. cron job: scheduled run every 10 minutes,

2. DNS A record TTL: now the TTL has to be set to 600 to have it refreshed every 10 mins

 

Is it a viable modification or should I have to consider anything else?
Any toughts about it?

Thanks in advance for your advice!
Luca

Iron Contributor

Hi @Jean-Benoit Paux 
about your different architecture, I think that the focus is on the scenario:

1. original @Pierre Roman scenario: more work on the client side to check if current IP is different from the A Record --> therefore less Azure Function triggers (only when update is needed) --> better choice if client can manage all these checks and in scenarios with a very large number of clients and frequent changes (e.g.: 5.000 devices that update every 10 mins) to avoid overloading Azure Function,

2. your scenario: more work on the Cloud side to check if the caller IP (the current client IP) is different from the A Record --> best choice with less sophisticated client devices that cannot handle complex scripts and can only call the Azure Function URL in a scheduled way every specified time

 

What do you think about it?

 

Co-Authors
Version history
Last update:
‎Jun 02 2021 09:36 PM
Updated by: