cmc/cleberg.net
My personal web garden & blog.
clone: git clone https://gitbay.org/cmc/cleberg.net.git
main: content/blog/2022-02-10-njalla-dns-api.org · raw
1#+date: [2022-02-10 Thu 00:00:00]
2#+title: Dynamic DNS Updates via the Njalla API
3#+description: A Python script to update DNS records automatically via the Njalla API.
4#+slug: njalla-dns-api
5#+filetags: :security:
6
7* Njalla's API
8
9As noted in my recent post about [[https://cleberg.net/blog/ditching-cloudflare/][switching to Njalla from Cloudflare]], I was
10searching for a way to replace my [[https://cleberg.net/blog/cloudflare-dns-api.html][bash script]] to update my domain's =A= record
11with my home's internet protocol (IP) address dynamically.
12
13To reiterate what I said in those posts, this is a common necessity for those of
14us who have non-static IP addresses that can change at any moment due to
15internet service provider (ISP) policy.
16
17In order to keep a home server running smoothly, the server admin needs to have
18a process to constantly monitor their public IP address and update their
19domain's DNS records if it changes.
20
21This post explains how to use Python to update Njalla's DNS (domain name system)
22records whenever a machine's public IP address changes.
23
24** Creating a Token
25
26To use Njalla's API, you will first need to create a token that will be used to
27authenticate you every time you call the API (application programming
28interface). Luckily, this is very easy to do if you have an account with Njalla.
29
30Simply go the [[https://njal.la/settings/api/][API Settings]] page and click the =Add Token= button. Next, enter a
31name for the token and click =Add=.
32
33Finally, click the =Manage= button next to your newly created token and copy the
34=API Token= field.
35
36** Finding the Correct API Request
37
38Once you have a token, you're ready to call the Njalla API for any number of
39requests. For a full listing of available requests, see the [[https://njal.la/api/][Njalla API
40Documentation]].
41
42For this demo, we are using the =list-records= and =edit-record= requests.
43
44The =list-records= request requires the following payload to be sent when
45calling the API:
46
47#+begin_src txt
48params: {
49 domain: string
50}
51#+end_src
52
53The =edit-record= request requires the following payload to be sent when calling
54the API:
55
56#+begin_src txt
57params: {
58 domain: string
59 id: int
60 content: string
61}
62#+end_src
63
64* Server Set-Up
65
66To create this script, we will be using Python. By default, I use Python 3 on my
67servers, so please note that I did not test this in Python 2, and I do not know
68if Python 2 will work for this.
69
70** Creating the Script
71
72First, find a suitable place to create your script. Personally, I just create a
73directory called =ddns= in my home directory:
74
75#+begin_src sh
76mkdir ~/ddns
77#+end_src
78
79Next, create a Python script file:
80
81#+begin_src sh
82nano ~/ddns/ddns.py
83#+end_src
84
85The following code snippet is quite long, so I won't go into depth on each part.
86However, I suggest you read through the entire script before running it; it is
87quite simple and contains comments to help explain each code block.
88
89*Note*: You will need to update the following variables for this to work:
90
91- =token=: This is the Njalla API token you created earlier.
92- =user_domain=: This is the top-level domain you want to modify.
93- =include_subdomains=: Set this to =True= if you also want to modify subdomains
94 found under the TLD (top-level domain).
95- =subdomains=: If =include_subdomains= = =True=, you can include your list of
96 subdomains to be modified here.
97
98#+begin_src python
99#!/usr/bin/python
100# -*- coding: utf-8 -*-
101# Import Python modules
102
103from requests import get
104import requests
105import json
106
107# Set global variables
108
109url = 'https://njal.la/api/1/'
110token = '<your-api-token>'
111user_domain = 'example.com'
112include_subdomains = True
113subdomains = ['one', 'two']
114
115
116# Main API call function
117
118def njalla(method, **params):
119 headers = {'Authorization': 'Njalla ' + token}
120 response = requests.post(url, json={'method': method,
121 'params': params}, headers=headers).json()
122 if 'result' not in response:
123 raise Exception('API Error', response)
124 return response['result']
125
126
127# Gather all DNS records for a domain
128
129def get_records(domain):
130 return njalla('list-records', domain=user_domain)
131
132
133# Update a DNS record for a domain
134
135def update_record(domain, record_id, record_content):
136 return njalla('edit-record', domain=domain, id=record_id,
137 content=record_content)
138
139
140# Get public IP addresses
141
142ipv4 = get('https://api.ipify.org').text
143print('IPv4: {}'.format(ipv4))
144ipv6 = get('https://api64.ipify.org').text
145print('IPv6: {}'.format(ipv6))
146
147# Call API to get all DNS records
148
149data = get_records(user_domain)
150
151# Loop through records and check if each one is IPv4 (A) or IPv6 (AAAA)
152# Update only if DNS is different from server IP
153
154for record in data['records']:
155 if record['name'] == '@' or (include_subdomains and record['name'] \
156 in subdomains):
157 if record['type'] == 'A':
158 if record['content'] == ipv4:
159 print(record['type'], 'record for', record['name'],
160 'already matches public IPv4 address. Skipping...'
161 )
162 else:
163 print('IPv4 of', ipv4,
164 'does not match Njalla's value of',
165 record['content'], '. Updating...')
166 update_record(user_domain, record['id'], ipv4)
167 elif record['type'] == 'AAAA':
168 if record['content'] == ipv6:
169 print(record['type'], 'record for', record['name'],
170 'already matches public IPv6 address. Skipping...'
171 )
172 else:
173 print('IPv6 of', ipv6,
174 'does not match Njalla's value of',
175 record['content'], '. Updating...')
176 update_record(user_domain, record['id'], ipv6)
177#+end_src
178
179** Running the Script
180
181Once you've created the script and are ready to test it, run the following
182command:
183
184#+begin_src sh
185python3 ~/ddns/ddns.py
186#+end_src
187
188** Setting the Script to Run Automatically
189
190To make sure the scripts run automatically, add it to the =cron= file so that it
191will run on a schedule. To do this, open the =cron= file:
192
193#+begin_src sh
194crontab -e
195#+end_src
196
197In the cron file, paste the following at the bottom of the editor in order to
198check the IP every five minutes:
199
200#+begin_src sh
201*/5 * * * * python3 /home/<your_username>/ddns/ddns.py
202#+end_src