A least privilege dynamic DNS server
Project description
anemoi
Anemoi is a least privilege dynamic DNS server. See the blog post for more info.
Installation
For production systems, install with:
pip install anemoi-dns
For development purposes, clone and install locally:
git clone https://github.com/dayt0n/anemoi && cd anemoi
pip install -e .
Usage
Configuration
Domains and backends are specified with a YAML configuration file. An example config file is provided at example_config.yml.
Domains
You can have multiple domains on one Anemoi instance. To do this, create a config.yml
file that looks something like this:
domains:
- zone: random-domain.org
provider: cloudflare
token: AAAAAAAAAAAAAAAAAAAAAAAAAAA
- zone: mydomain.com
provider: cloudflare
email: admin-user@yourdomain.com
key: asfdasfdasddfasddfasdfasdf
- zone: website.com
provider: porkbun
apikey: pk1_asdfasdfasdfasdfadsf
secret: sk1_lkjhlkjhlkjhlkjhlkjh
The provider
field can be any of:
cloudflare
- takes:
token
ORemail
+key
- takes:
porkbun
- takes:
apikey
+secret
- takes:
Backend
A backend must be specified in the config file like:
backend:
type: database
vendor: sqlite
path: /home/me/my-sqlite.db
type
can be one of:
tinydb
database
vendor
is only necessary for database
(for now) and can be one of:
sqlite
postgres
path
is either a file path or full database connection URL.
Running the server in development
All commands require you to use a -c /path/to/config.yml
unless you want to use the default config path.
anemoi -c /path/to/config.yml -v server
Running the server in production
You can use gunicorn to run the server after installing Anemoi:
gunicorn -b 0.0.0.0:80 'anemoi.server:setup_server("/path/to/config.yml")'
Creating a new client
To create a new client, run:
anemoi -c /path/to/config.yml client add -d yoursub.domain.com
This will give you a UUID and secret to use.
Deleting a client
If you believe a client has been compromised, you can revoke its access by deleting it.
To delete a client, run:
anemoi client delete -d yoursub.domain.com
Listing current clients
To see a list of current registered clients, run:
anemoi client list
Running a client
A client is just a fancy word for a single web request. The request must contain a JSON uuid
and secret
field, and that's it. It can be done using a curl
command:
curl -X POST http://an.anemoi-server.com:9999/check-in -H 'Content-Type: application/json' \
-d '{"uuid":"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "secret":"averylongsecrethere"}'
Development
Before adding any pull requests, make sure you have pre-commit
installed, then add the hooks for this repo:
pre-commit install
Anemoi allows you to have multiple DNS provider types as well as backend types to store your client data.
Providers
Adding a new DNS provider should be fairly simple.
Let's say there is a DNS provider, like Cloudflare, called Groundwater
. To add Groundwater as a dynamic DNS provider, do the following:
- Create a file called
anemoi/providers/groundwater.py
. - Add a class in that file called
GroundwaterProvider(Provider)
. The class should have a skeleton like:
class GroundwaterProvider(Provider):
key: str = ""
def __init__(self, config):
# parse config to get Groundwater API keys and such, return None on failure
if key := config.get("key"):
self.key = key
else:
return None
# returns list of {'A': '1.1.1.1'} objects
def get_record_ips(self, subdomain) -> List[Dict[str, str]]:
# query API here, then return the records as a dictionary
result = requests.get(f"https://groundwater.dev/api/get_records/{subdomain}").json()["records"]
"""
imagine the result looks like:
[
{
"domain":"test.groundwater-test.dev",
"type": "A",
"ip": "1.1.1.1",
"ttl": 600,
}
]
"""
return [{x['type']: x['ip']} for x in records]
# returns bool of if the update succeeded or not
def update_record_ip(self, subdomain: str, ip, rtype="A") -> bool:
if not is_ip_record_valid(ip, rtype):
return False
# parse out domain name, then update the IP with the record type rtype
# on the Groundwater API here
records = requests.get(f"https://groundwater.dev/api/get_records/{subdomain}").json()["records"]
if not records:
# create new record
result = requests.post(f"https://groundwater.dev/api/create_record/{subdomain}/{rtype}/{ip}").json()
if result.get("status") == "success":
return True
return False
# update existing record
for record in records:
if ip == record["ip"]:
# don't update record if not necessary
continue
result = requests.post(f"https://groundwater.dev/api/update_record/{subdomain}/{rtype}/{ip}").json()
if result.get("status") != "success":
return False
return True
- Use your provider in the config:
domains:
- zone: groundwater-test.com
key: asdfasdflkjhlkjh
provider: groundwater
Backends
All data storage backends must inherit the Backend
class. The skeleton of the backend should implement the following methods:
class YourBackend(Backend):
def __init__(self, config: Dict):
# do something with your {'type':'aaa', 'vendor': 'bbb', 'path': 'ccc'} config here
pass
def add_client(self, client: Client):
pass
# return UUID if success, None if fail
def delete_client(self, client: Client) -> Optional[str]:
return None
# return Client() object if success, None if fail
def get_client(
self, uuid: Optional[str] = None, domain: Optional[str] = None
) -> Optional[Client]:
return None
def update_ip(self, client: Client, ip: str, version: int):
pass
@property
def clients(self) -> List[Client]:
return []
anemoi.backends.database
and anemoi.backends.tinydb
may be useful to look at as you are creating your new data storage backend.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file anemoi_dns-1.0.1.tar.gz
.
File metadata
- Download URL: anemoi_dns-1.0.1.tar.gz
- Upload date:
- Size: 16.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.1.1 CPython/3.12.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9fa136f7b2b2b06ebe1faee3767ea02aea910f506eed54b3cd8b78fa9577ba27 |
|
MD5 | ff3e684922caffe63a59cf41754be4fa |
|
BLAKE2b-256 | bc68d49d69fac25b128be9be3f1337ca5660f3a6625889f716f84108c51360d8 |
File details
Details for the file anemoi_dns-1.0.1-py3-none-any.whl
.
File metadata
- Download URL: anemoi_dns-1.0.1-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.1.1 CPython/3.12.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1325964bfd7a499a1c2d3f60bfe58a63963adc0317f9cbe6b3c3255759d61c86 |
|
MD5 | d339664c1d61393bc651e7e3da815745 |
|
BLAKE2b-256 | 13a028c0ed59be12fff806681dc50f6851cc612fe5c5fd3f015bf9546384cef0 |