« ""
"Azure Dynamic DNS"

Since DynDns announced they were withdrawing their free offering, I have been looking for an alternative.

Turns out Microsoft’s Azure DNS has a REST API, and python library. While not free, it is very cheap – so far my DNS costs with Azure are running at 1p/day (although I only incur 5k queries a day).

Setting up Azure and a DNS zone is pretty straightforward, getting the authentication and python script working as a bit more tricky, so here’s what I did.

  1. First set up the DNS zone in your Azure Portal
  2. Now Create an App:
    1. Go to Azure AD, then “App Registrations”, then “New Registration”.
    2. Name it something meaningful for you, like “autodns”
    3. I chose “Single Tenant”
    4. The next page gives two of the magic ids you need:
      • Application/Client Id
      • Directory/Tenant Id
    5. Then on the left menu, go to “Certificates and Secrets”
    6. Create a new client secret – choose the expiry you want
    7. Copy and store the client secret – this is the only time it’s show in in full
  3. Then you need to give this app permissions to edit DNS records:
    1. Navigate to your DNS Zone
    2. Go to Access Control (IAM)
    3. Add Role assignment
    4. Choose “DNS Zone Controller”
    5. Type in the app name you create above (autodns, or whatever).
  4. Back in the DNS Zone Overview, make a note of the Resource Group and Subscription – you’ll need these later.

That’s it for the Azure side, and you’re ready to write the script to do the update.

My script is python – you’ll need the “azure” libraries


$ pip install azure

First job is to obtain an access token for the API, using the ServicePrincipalCredentials.

import adal
from azure.common.credentials import ServicePrincipalCredentials

client_id = '<value from app registration page>'
client_secret = '<secret from certifications and secret>'
tenant = '<value from app regsistration page>'

def authenticate_client_key():
  credentials = ServicePrincipalCredentials(
    client_id = client_id,
    secret = client_secret,
    tenant = tenant
  )
  return credentials

This can then be used to interact with the Azure api – in our application it’s the DNS Management we are interested in:

from azure.mgmt.dns import DnsManagementClient

resourceGroupName='<Value from DNS Zone page>'
subscription_id='<Value from DNS Zone page>'
dnsZone='yourdomain.com'
dnsRecord='yourhost'

cred = authenticate_client_key()
dns_client = DnsManagementClient(
  cred,
  subscription_id
  )

# Get the 'A' Record
res = dns_client.record_sets.get(resourceGroupName, dnsZone, dnsRecord, 'A')

print(res.arecords[0].ipv4_address);

# Update the A record
dns_client.record_sets.create_or_update(
  resourceGroupName,
  dnsZone,
  dnsRecord,
  'A',
  {"ttl": 300, "arecords": [{"ipv4_address": '1.2.3.4'}]}
  )

In my application, I’ve got a server running behind a NAT router on my broadband, and when my public IP address changes I want to update a DNS record to this address.

The missing piece here is to determine my public IP address – I’m currently using “ipify”.

from requests import get
currentIp = get('https://api.ipify.org').text

Glueing it all together, you end up with:

import adal
from msrestazure.azure_exceptions import CloudError
from azure.common.credentials import ServicePrincipalCredentials
from azure.mgmt.dns import DnsManagementClient
from requests import get

client_id = '<value from app registration page>'
client_secret = '<secret from certifications and secret>'
tenant = '<value from app regsistration page>'

resourceGroupName='<Value from DNS Zone page>'
subscription_id='<Value from DNS Zone page>'
dnsZone='yourdomain.com'
dnsRecord='yourhost'

def authenticate_client_key():
  credentials = ServicePrincipalCredentials(
    client_id = client_id,
    secret = client_secret,
    tenant = tenant
  )
  return credentials

currentIp = get('https://api.ipify.org').text
print('Current Public IP is {}.'.format(currentIp))

cred = authenticate_client_key()
dns_client = DnsManagementClient(
  cred,
  subscription_id
  )

# Get the 'A' Record
res = dns_client.record_sets.get(resourceGroupName, dnsZone, dnsRecord, 'A')

dnsIp = res.arecords[0].ipv4_address;
print('Current DNS record for {}.{} is {}.'.format(dnsRecord, dnsZone, currentIp))

if dnsIp != currentIp:
  print('Updating IP Address')
  dns_client.record_sets.create_or_update(
    resourceGroupName,
    dnsZone,
    dnsRecord,
    'A',
    {"ttl": 300, "arecords": [{"ipv4_address": currentIp}]}
    )
else:
  print('No update needed')

The code above also may well have syntax errors, as I’ve just typed it in – but the approach is pretty clear.

Obviously for production code, you’d need to add exception handling, logging, and notifications. You are also effectively trusting “ipify” with the ability to set your DNS A Record!

Then you can drop it on your server wrapped up in a crontab, or task schedule, or whetever takes your fancy.

Oh yes, and I’m not sure what the API limits are for Azure, so you could always cache the ‘dnsIp’ or do an nslookup rather than hitting Azure every run.

W Wordpress