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