Deploying a DDNS updater cron job using Ansible
[programming
IaaC
Ansible
DDNS
DNS
]
Recently, I’ve deployed this website on my RPi k3s cluster. I manage my DNS records using Cloudflare.
Even though my public IP address does not change that much, I wanted to make sure that it is periodically updated.
That’s when I searched around and stumbled accross a very simple script which suits my needs. The script makes use of the Cloudflare API which is very well written and easy to use, so it gives me hopes that it will be supported and maintained in the following years. I’m sure there are many more robust ways of achieving the same thing but this is more than enough for my setup.
The problem, though, is that the script requires secrets which I would not like to push to my infrastructure repository. Only the generated script should contain secrets but this final script version will only be accessible by an RPi which will periodically run the script.
Since I’ve already wrote a lot of my automations using Ansible, writing another playbook was a natural choice.
My Ansible playbook first generates a bash script from a Jinja template which populates data using Vault. This script is then used in a cronfile which gets executed every hour.
Here are my Ansible tasks:
- name: Create DDNS crontab directory
ansible.builtin.file:
path: "/var/cron"
state: directory
mode: '0755'
register: app_dir
- name: Prepare the DDNS script
ansible.builtin.template:
src: "{{ role_path }}/files/cloudflare.sh.j2"
dest: /var/cron/cloudflare.sh
mode: '0755'
vars:
cloudflare_configuration: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secrets/data/cloudflare token={{ vault_token }} url=http://vault.rpirack.home') }}"
- name: Create a cron file under /etc/cron.d
ansible.builtin.cron:
name: DDNS update
special_time: "hourly"
user: root
job: "/bin/bash /var/cron/cloudflare.sh"
The vault_token
variable is retrieved from a user prompt declared in the playbook:
- name: Setup DNS
hosts: dns
vars_prompt:
- name: vault_token
prompt: Enter the Vault token
become: true
roles:
- dns
handlers:
- ansible.builtin.import_tasks: handlers/main.yml
As for the Jinja template cloudflare.sh.j2
itself, I’ve tried to stay to the original as close as possible, the
only thing changed is the Vault secret injection at the beginning of the script:
#!/bin/bash
## change to "bin/sh" when necessary
set -eu
auth_email="{{ cloudflare_configuration['auth_email'] }}" # The email used to login 'https://dash.cloudflare.com'
auth_method="global" # Set to "global" for Global API Key or "token" for Scoped API Token
auth_key="{{ cloudflare_configuration['auth_key'] }}" # Your API Token or Global API Key
zone_identifier="{{ cloudflare_configuration['zone_identifier'] }}" # Can be found in the "Overview" tab of your domain
record_name="{{ cloudflare_configuration['record_name'] }}" # Which record you want to be synced
ttl=$((60 * 60 * 4)) # Set the DNS TTL (seconds)
proxy="true" # Set the proxy to true or false
sitename="Zezulka personal website" # Title of site "Example Site"
slackchannel="" # Slack Channel #example
slackuri="" # URI for Slack WebHook "https://hooks.slack.com/services/xxxxx"
discorduri="" # URI for Discord WebHook "https://discordapp.com/api/webhooks/xxxxx"
###########################################
## Check if we have a public IP
###########################################
<hidden>
After running the playbook, a new cron job is defined for root:
rpi_admin@raspberrypi:~ $ sudo crontab -l
[sudo] password for rpi_admin:
#Ansible: DDNS update
@hourly /bin/bash /var/cron/cloudflare.sh
Notice the special keyword @hourly
which is part of the vanilla cron; this way, I do not need to use standard cron expression with numbers and stars.
After running for a few hours, I can clearly see that the cronjob works as expected without needing to do anything else:
rpi_admin@raspberrypi:~ $ cat /var/log/messages | grep DDNS
Jul 7 01:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 02:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 03:00:04 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 04:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 05:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 06:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 07:00:04 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 08:00:06 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 09:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 10:00:06 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 11:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 12:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.
Jul 7 13:00:05 DDNS Updater: IP (<hidden>) for zezulka.dev has not changed.