monogram with initials UKR

Dynamic DNS Using Azure DNS Zones And Logic Apps

Updated: Under: projects Tags: #Azure #HomeLab

Due to some circumstances I wasn’t happy with my DNS setup, which was very basic I used Netlify DNS & as for Dynamic DNS, I used duckdns.org as a CNAME to a subdomain, which worked really well. My only gripe was downtimes with DuckDNS.

When searching for a Dynamic DNS solution I was a bit blown away by the subscription prices. Instead I decided to use my most comfortable cloud provider, that being Azure.

Requirements

To make sure there is none of that inevitable scope creep I wrote down a list of requirements that I need to fullfil.

Functional:

  1. One Stop Shop For all DNS management for domain.
  2. Dynamically update subdomain & wildcard subdomain records.
  3. Be able to dynamically set local IP Address using cron.

Non-Functional:

  • Cheap!
  • Easy to maintain // low-or-no-code.

Part 1 - General DNS Management.

This was simple, setting up Azure DNS Zones was straight forward with which I was able to quickly setup my website & email DNS records.

Azure DNS Zones Screenshot

Part 2 - Dynamic DNS Mechanism.

Dynamic DNS is not provided by Azure :( But dynamic DNS is no voo-doo magic, it’s simply a DNS record with a low TTL(time to live). In simple terms if you manually update the entry on the Azure DNS portal with a low TTL that gets the job done.

But we don’t want to do that, I have seen a couple of DynDNS solutions using Azure Functions, but personally that felt too overkill. I was lazy and I need the easy way out! The Solution is Azure Logic Apps, Logic Apps are simply code-less event based functions that can be designed on the portal.

Azure Logic App for Dynamic DNS Updating
Shown above is the Azure Logic App that does the dynamic DNS updating. This is archived using the latest preview feature ARM Deploy, ARM being Azure Resource Manager which under the hood manges all Azure resources. This Logic App Does the following,

  1. Accepts HTTP Post Requests using a secured endpoint.
  2. Takes IP address given in request and updates Azure DNS subdomain A records.
  3. Responds with deployment status.

given below is the code view of my logic app. This is not directly compatible with another account, therefore this is to be used as a guide to configure the elements in the logic apps.

{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "actions": {
            "Respond_with_200": {
                "inputs": {
                    "body": {
                        "status": "@{body('Update_DNS_Entries')?['properties']?['provisioningState']}",
                        "success": true
                    },
                    "headers": {
                        "Content-Type": "application/json; charset=utf-8"
                    },
                    "statusCode": 200
                },
                "kind": "Http",
                "runAfter": {
                    "Update_DNS_Entries": [
                        "Succeeded"
                    ]
                },
                "type": "Response"
            },
            "Update_DNS_Entries": {
                "inputs": {
                    "body": {
                        "properties": {
                            "mode": "Incremental",
                            "template": {
                                "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                "contentVersion": "1.0.0.0",
                                "parameters": {
                                    "destination_ip": {
                                        "defaultValue": "@{triggerBody()?['ip']}",
                                        "type": "String"
                                    },
                                    "dnszones_name": {
                                        "defaultValue": "ukr.lk",
                                        "type": "String"
                                    }
                                },
                                "resources": [
                                    {
                                        "apiVersion": "2018-05-01",
                                        "name": "[concat(parameters('dnszones_name'), '/home')]",
                                        "properties": {
                                            "ARecords": [
                                                {
                                                    "ipv4Address": "[parameters('destination_ip')]"
                                                }
                                            ],
                                            "TTL": 600,
                                            "targetResource": {}
                                        },
                                        "type": "Microsoft.Network/dnszones/A"
                                    },
                                    {
                                        "apiVersion": "2018-05-01",
                                        "name": "[concat(parameters('dnszones_name'), '/*.home')]",
                                        "properties": {
                                            "ARecords": [
                                                {
                                                    "ipv4Address": "[parameters('destination_ip')]"
                                                }
                                            ],
                                            "TTL": 600,
                                            "targetResource": {}
                                        },
                                        "type": "Microsoft.Network/dnszones/A"
                                    }
                                ],
                                "variables": {}
                            }
                        }
                    },
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['arm']['connectionId']"
                        }
                    },
                    "method": "put",
                    "path": "/subscriptions/@{encodeURIComponent('<sub_id>')}/resourcegroups/@{encodeURIComponent('ukr-rg-person-dns-eastasia')}/providers/Microsoft.Resources/deployments/@{encodeURIComponent('update_ip')}",
                    "queries": {
                        "wait": true,
                        "x-ms-api-version": "2016-06-01"
                    }
                },
                "runAfter": {},
                "type": "ApiConnection"
            }
        },
        "contentVersion": "1.0.0.0",
        "outputs": {},
        "parameters": {
            "$connections": {
                "defaultValue": {},
                "type": "Object"
            }
        },
        "triggers": {
            "manual": {
                "inputs": {
                    "schema": {
                        "properties": {
                            "ip": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    }
                },
                "kind": "Http",
                "type": "Request"
            }
        }
    },
    "parameters": {
        "$connections": {
            "value": {
                "arm": {
                    "connectionId": "/subscriptions/<sub_id>/resourceGroups/ukr-rg-person-dns-eastasia/providers/Microsoft.Web/connections/arm",
                    "connectionName": "arm",
                    "id": "/subscriptions/<sub_id>/providers/Microsoft.Web/locations/eastasia/managedApis/arm"
                }
            }
        }
    }
}

Important lines to keep note (update based on your requirement),

  • Line Number 38 - The Name of your DNS Zone as in the domain name.
  • Line Number 45 & 59 - The DNS entry path, in this use-case the A record is updated to the home subdomain.

Part 3 - Updating records using cron.

With my router having no custom dynamic IP options, the task was on a Raspberry PI. This was fairly simple, I whipped up a bash script and then added it on to crontab.

Given below is the following script, please do change the webhook URL with your own.

#!/bin/bash
HIST_FILE=/tmp/dyn_ip_cur
WEB_HOOK="<enter webhook url>"

# Create Previous IP File if not present
if [ ! -f "$HIST_FILE" ]; then
    echo "The file $HIST_FILE does not exist. Creating!"
    echo "0.0.0.0" > $HIST_FILE
fi

# Fetch Previous IP From File
PRV_IP=$(cat $HIST_FILE)

# Get Current IP
CUR_IP=$(curl https://ip.42.pl/raw)

# Update Current IP to file
echo "$CUR_IP" > $HIST_FILE

echo "$PRV_IP -> $CUR_IP"

if [ $CUR_IP != $PRV_IP ];
then
    echo "IP has updated to $CUR_IP , updating on Azure"
    curl -L -X POST "$WEB_HOOK" \
        -H 'Content-Type: application/json' \
        --data-raw "{
            \"ip\" : \"$CUR_IP\"
        }"
fi

and in the crontab

*/5 * * * * /root/azuredns/az.sh >/dev/null 2>&1

Show Me The Money!

I got to emphasize, that this will depend on your usage & traffic. For my modest single user usage and not so pop wesbite I get around with $0.50 a month, which I think is pretty great!

Azure DNS Zones Screenshot

Conclusion

Azure is great, in this case Logic Apps. I was able to archive all my functional and non-functional requirements! Hope this was helpful and I was able to nudge someone in the correct direction.