If you know me, you know that I have serious, unresolved trust issues. Not so much with people, but with machines.
As soon as I hand off a VM or physical machine to someone, I assume the data on that machine is no longer fully mine even if I went to great lengths to secure the data at rest. There has to be a plan if the data (API credentials) on that machine are compromised and it shouldn’t involve rotating every credential under the sun.
The DNS provider privilege problem
I have a few VPN endpoints scattered across the southeastern United States. Some of these endpoints are in residential areas, so their IPs tend to change from time to time.
Dynamic DNS (DDNS) is the obvious solution here, but existing DDNS clients require placing an API key on a machine at each site. That API key potentially has control over either my entire account on the DNS service or at the very least, the target DNS zone. If any site’s VPN appliance was compromised, the attacker would have the keys to my public infrastructure castle.
Those dang API keys have too much access
I would take an actual bullet for the Porkbun pig.
This post is absolutely not sponsored by them, I just like the service. They make it easy to buy cheap domain names with almost any TLD on the market. I didn’t dive into their API system until this project, but in doing so I discovered that it leaves a lot to be desired in the way of security.
Setting up a Porkbun API key is fairly straightforward:
- Go to https://porkbun.com/account/api.
- Name it
Notice how there is no mention of limiting an API key to specific capabilities other than a message at the top of the page that instructs you to turn on API access on a per-domain basis:
So, let’s turn on the API access for a particular domain:
This is cool and all, but there is a slight issue: any API key I create will have access to any domain that has API access turned on.
Ideally, I should have something that can handle this sort of unscoped access, but at an even more granular level. Then I could do DDNS on hosts that I trust much less than a machine in my own house that only I have access to.
For example, let’s say you have a couple of networks that you want VPN access to with the domain names egress.business-name-1.mydomain.com
and egress.business-name-2.mydomain.com
. In the Porkbun API’s current state, you could place API keys that have access to your entire account on the on-site servers and run a simple dynamic DNS client on each one. But you don’t really want to do that, right?
Yes, you can encrypt the drives, put the server in a room with 12 deadbolts on a steel door, and STIG it until you can only log in from 3:00 to 3:15 PM on the 2nd Wednesday of each month. But that will never be enough. What if we get another OpenSSH RCE vuln that takes days to exploit?
What we really need here is a second layer of security. And this second layer should easily work with other DNS providers besides Porkbun, such as Cloudflare. That’s where Anemoi comes in.
The solution: Anemoi
Anemoi is an extendible least privilege Dynamic DNS server. You can limit the capabilities of your zone-editing on untrusted machines down to the FQDN. Nothing (except curl
or wget
) needs to be installed on the client machines that will check in with the server to keep their records up-to-date.
If a DDNS client machine is compromised, the most an attacker can do is change the DNS record for the specific FQDN that the machine is responsible for updating. Once the Anemoi server operator knew of the client’s compromise, they could just remove the client from Anemoi’s known client list and the attacker would be prevented from further updating the FQDN record. At no point in this process would the main DNS provider API key need to be rotated.
Depending on the DNS provider used, Anemoi can greatly enhance the security of your API keys.
In Porkbun’s case, using Anemoi for DDNS will prevent someone from not only compromising an entire domain (e.g. *.mydomain.com
), but also any other domain that you have through Porkbun that has API Access enabled.
Cloudflare gives you the option to limit your API key to a specific DNS zone:
Combined with Anemoi, you can further limit the DDNS client machine to only update A/AAAA records for my-subdomain.mydomain.com
instead of any record type on Cloudflare’s *.mydomain.com
zone.
Using Anemoi
First, install the latest version of Anemoi from PyPi:
1
pip install anemoi-dns
All of the configuration for the Anemoi server is done in a YAML config file. This config file looks like:
1
2
3
4
5
6
7
8
9
10
11
12
13
domains:
- zone: mydomain.com
provider: cloudflare
token: AAAAAAAAAAAAAAAAAAAA
- zone: my-other-domain.com
provider: porkbun
apikey: pk1_AAAAAAAAAAAAAAAAAAA
secret: sk1_BBBBBBBBBBBBBBBBBBB
backend:
type: database
vendor: sqlite
path: /path/to/test-db.db
In production, you can run the server on port 80 with:
1
gunicorn -b 0.0.0.0:80 'anemoi.server:setup_server("/path/to/config.yml")'
Nginx forwarding and certs are left as an exercise to the reader.
For a development instance, use the anemoi server
command:
1
anemoi server -c /path/to/config.yml -p 8080
Anemoi allows you to choose from multiple data storage backends. I initially wrote it using just TinyDB to store client data. But recently I added different data storage options, like PostgreSQL, SQLite, and MySQL.
Adding a client can be done at any time, even while Anemoi is running as a service in the background. Let’s say you want to create credentials for a client to keep the egress.business-name-1.mydomain.com
up-to-date. All you have to do is:
1
2
3
4
5
$ anemoi client add -d egress.business-name-1.mydomain.com
- Client info -
uuid: d7a97488-60b5-4afa-9cfa-4d4864aa6e0d
secret: j5Y3bINOM8sz05sZ42Kp3hCs0UXE4ge5WpI6xeNLhW5uOxXPLTVwuFFyvEVmCyiDZgAtrDbK_QLc40vC6urMPA
Then, on the somewhat-untrusted client, create a cron job or systemd service that periodically runs the following command every few minutes:
1
2
curl -X POST https://an.anemoi-server.com/check-in -H 'Content-Type: application/json' \
-d '{"uuid":"d7a97488-60b5-4afa-9cfa-4d4864aa6e0d", "secret":"j5Y3bINOM8sz05sZ42Kp3hCs0UXE4ge5WpI6xeNLhW5uOxXPLTVwuFFyvEVmCyiDZgAtrDbK_QLc40vC6urMPA"}'
If one of your client machines is compromised, simply delete the client for that FQDN with:
1
anemoi client delete -d egress.business-name-1.mydomain.com
Further information on using Anemoi can be found on the Anemoi repo.
Nice-to-haves that we don’t have yet
It would be cool to have API keys stored in something like Vault, but for now they are just in the config file on the server. The basic idea of this project is that the server is the only thing you have 100% control over, so leaving the API keys on there is okay for now.
Extending the web interface to allow an admin to add clients from the Flask server would certainly make the client creation process easier for some folks. Initially, I did not add this because my client setup is part of an Ansible playbook which runs the client setup commands on the server and then passes the UUID/secret to the actual client in one go.
Of course, the more DNS providers Anemoi supports, the better.
Contributing
For the coders out there, adding a new DNS provider is fairly simple. The Anemoi repo has more detailed instructions, but you essentially need to:
- Create a new file in the project with the name of your provider, such as
cloudflare
. - Add a new class called
CloudflareProvider(Provider)
.- Write out the config file parsing to retrieve the API key or other custom fields you need.
- Implement the requests for retrieving/updating existing records and creating new records when necessary.
CloudflareProvider
and PorkbunProvider
already exist at the time of writing this post, so both should give you a good idea of what the code should look like.
If you want to make a different client data storage backend for some reason, that should be of equivalent difficulty.
PRs are welcome!
Shameless plug zone
I’ve been using Anemoi in production for about a year now. It is stable and I feel that it is mature enough to release into the wild, so have fun and make an issue on GitHub if you run into any trouble!
If you like Anemoi, feel free to support its development.
I do freelance / consulting work on the side, so if you or your business is interested in secure-by-design software and infrastructure, feel free to reach out!