content/blog/2022-02-10-njalla-dns-api.org

0b2895a90c7751ce06e4b26d4db41005ea2f40a9
cleberg.net/content/blog/2022-02-10-njalla-dns-api.org rendered · source · history · blame · raw

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