Hetzner Dedicated Server Reverse DNS + Ansible
Published:
Continuing on the path towards all my stuff being managed by Ansible, I’ve figured out a method of managing the reverse DNS entries for subnets on the Hetzner Dedicated Server.
There’s a bunch of Ansible modules for handling Hetzner Cloud, but these servers are managed in Robot which the Cloud API doesn’t cover. Instead, you need to use the Robot Webservice.
Ansible does have a module for doing pretty arbitrary things with web APIs though, so using that I’ve got the following playbook figured out to keep the reverse DNS entries in sync:
---
- hosts:
- vmhost_vm1
gather_facts: False
tasks:
- name: import hetzner webservice credentials
include_vars:
file: "hetzner_ws.yml"
- name: set rdns for hetzner hosts
block:
- name: get current rdns entry
uri:
user: "{{ hetzner_ws_user }}"
password: "{{ hetzner_ws_password }}"
url: "https://robot-ws.your-server.de/rdns/{{ vmip4 }}"
status_code: [200, 404]
register: rdns_result
- name: update incorrect rdns entry
uri:
user: "{{ hetzner_ws_user }}"
password: "{{ hetzner_ws_password }}"
url: "https://robot-ws.your-server.de/rdns/{{ vmip4 }}"
method: "POST"
body_format: form-urlencoded
body:
ptr: "{{ inventory_hostname }}"
status_code: [200, 201]
when: '"rdns" not in rdns_result.json or inventory_hostname != rdns_result.json.rdns.ptr'
changed_when: True
delegate_to: localhost
The host groups this runs on are currently hardcoded as the VMs that live in the Hetzner Dedicated Server. A future iteration of this might use some inventory plugin to look up the subnets that are managed on Hetzner and create a group for all of those. Right now it won’t be setting the reverse DNS for the “router” interface on that subnet, and won’t automatically include new server’s subnets.
Gathering facts is disabled because all of these run locally. There is at least one VM running on this server that I can’t log in to because I host it for someone else, so running locally is a necessity.
The webservice credentials are stored in an Ansible Vault encrypted YAML file and loaded explicitly. An important note: the webservice username and password is not the same as your regular Robot username and password. You need to create a webservice user in Robot under “Settings; Webservice and app settings”.
If you attempt to authenticate with an incorrect username and password 3 times in a row, your IP address will be blocked for 10 minutes. There are 6 hosts in this group, so I did this a few times before I realised there was a different user account I needed to create. I’d suggest limiting to a single host while you’re testing to get the authentication figured out.
The actual calls to the webservice take place in a
block
just to avoid having to specify the delegate_to: localhost
twice. The first
step looks up the current PTR record, and accepts success if it gives either
a 200 or 404 status code (it will be 404 if there is no existing pointer
record). The result of this is used to conditionally create/update the PTR
record in the next task.
If nothing needs to be done, nothing will be changed and the second task will be skipped. If a change is needed, the second step is successful if either the PTR record is updated (status 200) or created (status 201).
This was actually a lot easier than I thought it would be, and the uri module looks to be really flexible, so I bet there are other things that I could easily integrate into my Ansible playbooks.