{"id":20703144,"url":"https://github.com/sfttech/sftdyn","last_synced_at":"2025-04-05T03:09:03.036Z","repository":{"id":17170240,"uuid":"19937392","full_name":"SFTtech/sftdyn","owner":"SFTtech","description":"Self-hosted dyndns/dynamic DNS server and updater for bind","archived":false,"fork":false,"pushed_at":"2023-07-31T15:08:56.000Z","size":80,"stargazers_count":260,"open_issues_count":2,"forks_count":58,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-03-29T02:04:38.855Z","etag":null,"topics":["bind","ddns","dynamic-dns-server","dynamic-dns-solution","dyndns","dyndns-service","named","nsupdate","python","simple","simple-stupid","zone-update"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SFTtech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2014-05-19T10:14:03.000Z","updated_at":"2025-01-08T05:05:54.000Z","dependencies_parsed_at":"2022-08-07T08:15:48.903Z","dependency_job_id":"da03cae9-d544-4450-a1db-b4f82b240f31","html_url":"https://github.com/SFTtech/sftdyn","commit_stats":{"total_commits":70,"total_committers":7,"mean_commits":10.0,"dds":0.2571428571428571,"last_synced_commit":"0011266afa98181c2f9fabe3a5ad206e499bb57a"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SFTtech%2Fsftdyn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SFTtech%2Fsftdyn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SFTtech%2Fsftdyn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SFTtech%2Fsftdyn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SFTtech","download_url":"https://codeload.github.com/SFTtech/sftdyn/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247280272,"owners_count":20912967,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bind","ddns","dynamic-dns-server","dynamic-dns-solution","dyndns","dyndns-service","named","nsupdate","python","simple","simple-stupid","zone-update"],"created_at":"2024-11-17T01:06:27.143Z","updated_at":"2025-04-05T03:09:02.984Z","avatar_url":"https://github.com/SFTtech.png","language":"Python","readme":"# sftdyn dynamic dns server\n\n`sftdyn` is a minimalistic dynamic DNS server that accepts update requests via `http` or `https` and forwards them to a locally running DNS server via `nsupdate -l`.\nYou can use it to easily update IPs of hosts in a domain whose IPs are not static and change to unpredictable addresses.\n\nIt lets you easily create a dyndns.org-like service, using your own DNS server, and can (probably) be used with your router at home.\n\n## Operation\n\n* You have a domain, e.g. `sft.rofl`, and a subdomain for dynamic entries, e.g. `dyn.sft.rofl`\n* The device whose IP address you want to store submits a https request to the `sftdyn` server containing a secret token, in order to update `devicename.dyn.sft.rofl`\n* From this, the `sftdyn` server knows the request origin IP\n* From the secret token, `sftdyn` can associate a hostname to update its DNS record (`devicename.dyn.sft.rofl`)\n* The request therfore updated an IP in your zone\n\n\n## Requirements\n\n* [Python \u003e=3.5](https://www.python.org/)\n* [`aiohttp`](https://aiohttp.readthedocs.io/)\n\n\n## Setup Guide\n\n`sftdyn` is for you if you host a DNS zone and can run a Python server so it updates the nameserver records.\nThis guide assumes that you're using [BIND](https://en.wikipedia.org/wiki/BIND), your zone is `dyn.sft.rofl`, and your server's IP is `12.345.678.90`.\nSubstitute the correct values for zone and IP as you use this guide.\n\n\n### Nameserver\n\n`bind` has to be configured to serve the updatable zone.\n\nYou probably have a zonefile for `sft.rofl` already.\nYou need to delegate `dyn.sft.rofl` to the local nameserver.\n\nIn the `sft.rofl` zone, add `NS records` to the new dynamic zone we're about to create:\n\n```\n# so the dyn.sft.rofl zone is delegated to the nameserver running sftdyn.\n# likely you need the same NS record as for the sft.rofl zone itself.\ndyn 30m IN NS yournameserver's_a_record\n```\n\nNow let's create the `dyn.sft.rofl` zone, where all the dynamic records will live.\nSomewhere in `named.conf`, add the new dynamic zone:\n\n```\nzone \"dyn.sft.rofl\" IN {\n    type master;\n    file \"/etc/bind/dyn.sft.rofl.zone\";\n    journal \"/var/cache/bind/dyn.sft.rofl.zone.jnl\";\n    update-policy local;\n};\n```\n\n`/var/cache/bind` and `/etc/bind/dyn.sft.rofl.zone` must be writable for *bind*.\n\nCreate the empty zone file\n\n```\ncp /etc/bind/db.empty /etc/bind/dyn.sft.rofl.zone\n```\n\nWe also can define a hostname to send the IP update requests to within the `dyn.sft.rofl` zone, or even use `dyn.sft.rofl` itself.\n`@` means the zone name itself.\n\n```\n# within the dyn.sft.rofl zonefile, we set the IP for the dyn.sft.rofl host itself.\n# this is the ip of the nameserver itself, where sftdyn is running.\n# -\u003e you can then send update requests to https://dyn.sft.rofl/...\n@ 10m IN A 12.345.678.90\n@ 10m IN AAAA some:ipv6::address\n```\n\n\n### sftdyn server setup\n\nTo install *sftdyn*, use `pip install sftdyn` or `./setup.py install`.\n\nLaunch it with `python3 -m sftdyn [command-line options]`.\n\nConfiguration is by command-line parameters and conf file.\nA sample conf file is provided in `etc/sample.conf`.\nIf no conf file name is provided, `/etc/sftdyn/conf` is used.\nHostnames/update keys are specified in the conf file.\n\n`sftdyn` _should_ run under the same user as your DNS server, or it _might_\nnot be able to update it properly. Alternatively, to run sftdyn as the user of\nyour choice, see Advanced setup later in this article.\n\n\n#### systemd service\n\nTo run `sftdyn` automatically, you can use a systemd service.\n\nThe `sftdyn` distribution package should automatically install `sftdyn.service`.\n\nIf you have to manually install it, use the example unit `etc/sftdyn.service`\nand copy it to `/etc/systemd/system/sftdyn.service` on the `sftdyn` host machine.\n\nEnable the launch on boot and also start `sftdyn` now:\n\n```\nsudo systemctl enable --now sftdyn.service\n```\n\n#### Unencrypted operation\n\nYou _can_ use `sftdyn` in plain HTTP mode.\nYour average commercial dynamic DNS provider provides a HTTP interface, so most routers only support that.\n\nSomebody could grab your \"secret url\" with this and perform unintended updates of your record.\n\n\n#### Encrypted operation\n\nBecause of the above reason, you _should_ use HTTPS to keep your update url token secret.\nFor that, your server needs a X.509 key and certificate.\nYou can create those with [let's encrypt](https://letsencrypt.org/), buy those somewhere, or create a self-signed one.\n\n##### Reverse proxy\n\nYour server running `sftdyn` may already have a webserver (e.g. nginx) to handle other web requests.\nIt may already have proper certificates setup (e.g. with letsencrypt) - which you can just reuse for sftdyn.\n\nIf you have `nginx`, the following config block will redirect requests to `dyn.sft.rofl` to the `sftdyn` server.\n\nRemember to use the `X-Forwarded-For` header in the `sftdyn` config (in `get_ip`) as the client ip!\n\n```nginx\nserver {\n    server_name dyn.sft.rofl;\n\n    // ...\n\n    location / {\n        # with this line, nginx relays the request to sftdyn\n        proxy_pass http://localhost:8080/;\n\n        # remember the original ip - we need to extract it in get_ip\n        # in the sftdyn config then!\n        proxy_set_header X-Forwarded-For $remote_addr;\n        proxy_set_header Host            $host;\n    }\n\n    // ...\n}\n```\n\nAlternatively, you can add the location block with `location /dyn` or something to some existing server block.\n\nIn any way, you can then submit requests to the regular https port since you send to nginx now.\n-\u003e remove `:4443` in the client requests.\n\n\n##### Let's Encrypt\n\nIf you don't want to use a reverse proxy to terminate the tls connection, you can directly configure `sftdyn` to use the certificate.\nTo use a certificate by [Let's Encrypt](https://letsencrypt.org/) directly in `sftdyn`:\n\n```\n# in sftdyn.conf:\nkey = \"/etc/letsencrypt/live/host.name.lol/privkey.pem\"\ncert = \"/etc/letsencrypt/live/host.name.lol/fullchain.pem\"\n```\n\nMake sure the certificate is valid for the domain your `sftdyn` is getting requests for.\n\nA `https` request to `sftdyn` to update an IP will then be secure™ (e.g. with `curl`).\n\n\n##### Self-signed certificate\n\nTo generate `server.key` and a self-signed `server.crt` valid for 1337 days:\n\n```\nopenssl genrsa -out server.key 4096\nopenssl req -new -key server.key -out server.csr\nopenssl x509 -req -days 1337 -in server.csr -signkey server.key -out server.crt\nrm server.csr\n```\n\nMake sure you enter your server's domain name for _Common Name_ (the hostname you'll use for querying `sftdyn` with clients.\n\nA `https` request to `sftdyn` to update an IP will then be more secure™ than a globally valid certificate like from Let's Encrypt, but you'll need to transfer the `server.crt` to the device performing the request (e.g. with `curl`).\n\n\n### Client\n\nThe client is the device whose IP we want to update in the dynamic zone.\nCommon clients are your plastic router at home that changes it's DSL IP address from time to time.\n\nThe client triggers the IP update at the `sftdyn` server, so your DNS then delivers the correct IP.\n\n#### Plastic router\n\nCheap plastic routers often have built-in dynamic dns update support.\nSince `sftdyn` is not that well known, within the plastic router's web UI you need to select something like _user-defined provider_, and enter http://dyn.sft.rofl:8080/yourupdatekey as the update URL.\nWrite random stuff as name/user name/password, since just the update URL is the secret alone (tested with my AVM Fritz!Box. YMMV).\nMost routers don't support HTTPS update requests (especially not with custom CA-cert, so you'll probably need HTTP.\n\nIf you set up `sftdyn` with let's encrypt, https may work - just test it :)\n\n#### Request with `curl`\n\nIf you want to update the external IP of some NAT gateway (like home router, ...), and you have a machine in that network which can use `curl`, choose this client method.\n\nIf you use HTTPS with a let's encrypt certificate, `curl` will be happy to request with encryption\n\nIf you use a self-signed certificate, `curl` will refuse to talk to the server (because it obviously can't trust it without knowing it).\nTo make `curl` trust the self-signed certificate:\n - Copy `server.crt` to the client, and use `curl --cacert server.crt`.\nAlternatively, to let `curl` ignore the security problem and just accept whatever it gets:\n - Use `curl -k` to ignore the error (Warning: see the security considerations below).\n\nThe result codes mean the following:\n\n| HTTP code     | Text          | Response interpretation                         |\n| ------------- | ------------- | ----------------------------------------------- |\n| 200           | OK            | Update successful                               |\n| 200           | UPTODATE      | Update unneccesary                              |\n| 403           | BADKEY        | Unknown update key                              |\n| 500           | FAIL          | Internal error (see the server log)             |\n| 200           | _your ip_     | Returned if no association key is provided      |\n\n##### systemd timer\n\n`systemd` timers are like cronjobs. Use them to periodically run the update query.\n\nCreate `/etc/systemd/system/sftdynupdate.timer`:\n```\n[Unit]\nDescription=SFTdyn dns updater\n\n[Timer]\nOnCalendar=*:0/15\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n```\n\nCreate `/etc/systemd/system/sftdynupdate.service`:\n```\n[Unit]\nDescription=SFTdyn name update\n\n[Service]\nType=oneshot\nUser=nobody\nExecStart=/usr/bin/env curl -f -s --cacert /path/to/server.crt https://dyn.sft.rofl:4443/yoursecretupdatekey\n```\n\nActivate the timer firing with:\n\n```\nsudo systemctl enable --now sftdyn.timer\n```\n\nVerify the timer is scheduled:\n\n```\nsudo systemctl list-timers\n```\n\nTo manually trigger the update (e.g. for testing purposes):\n\n```\nsudo systemctl start sftdyn.service\n```\n\n##### Cronjob\n\nCronjobs are the legacy variant to periodically run a task, you could do this like this:\n\n```\n*/10 * * * * curl https://dyn.sft.rofl:4443/mysecretupdatekey\n```\n\n\n### Advanced setup\n\n#### Pre-generated keyfile\n\nBy default sftdyn uses a key auto-generated by bind, `/var/run/named/session.key`.\nThe permissions of this file may be reset on startup, and could be too\nrestrictive for sftdyn.\n\nIf you see errors such as these in `journalctl -u sftdyn`, it may indicate a\npermission issue with the keyfile:\n```\n; TSIG error with server: tsig indicates error\nupdate failed: NOTAUTH(BADSIG)\n```\n\nAn alternative approach is to use a pre-generated keyfile dedicated to sftdyn,\nwhich lets you have more control over the file permissions.\n\n##### Create a new key\n\nThe example script below generates a keyfile in `/etc/bind/keys/sftdyn.key`,\nand changes the user/group ownership to `bind:sftdyn`. Modify as needed to\nbest suit your specific setup.\n\n```sh\nb=$(dnssec-keygen -a hmac-sha512 -b 512 -n USER -K /tmp foo)\ncat \u003e /etc/bind/keys/sftdyn.key \u003c\u003cEOF\nkey \"sftdyn\" {\n    algorithm hmac-sha512;\n    secret \"$(awk '/^Key/{print $2}' /tmp/$b.private)\";\n};\nEOF\nrm -f /tmp/$b.{private,key}\nchown bind:sftdyn /etc/bind/keys/sftdyn.key # or whatever permissions\nchmod 640 /etc/bind/keys/sftdyn.key\n```\n\n##### Include the key in named.conf\n\n```\ninclude \"/etc/bind/keys/sftdyn.key\";\n```\n\n##### Configure named zone to use the key\n\n```\nzone \"dyn.sft.mx\" IN {\n    type master;\n    file \"/etc/bind/dyn.sft.mx.zone\";\n    journal \"/var/cache/bind/dyn.sft.mx.zone.jnl\";\n    allow-update { key \"sftdyn\"; };\n};\n```\n\n##### Change sftdyn configuration to use the key\n\nEdit the nskeyfile option in the configuration file, by default located in\n`/etc/sftdyn/conf`:\n\n```\nnskeyfile = \"/etc/bind/keys/sftdyn.key\"\n```\n\n## About\n\nThis software was written after the free `dyndns.org` service was shut down.\nAfter a week or so of using plain `nsupdate`, we were annoyed enough to decide to write this.\n\nThe main goal of this tool is to stay as minimal as possible; for example, we deliberately didn't implement a way to specify the hostname or IP that you want to update; just a simple secret update key is perfectly good for the intended purpose.\nIf you feel like it, you can make the update key look like a more complex request; every character is allowed.\nExample: `host=test.sft.rofl,key=90bbd8698198ea76`.\n\nThe conf file is interpreted as python code, so you can do arbitrarily complex stuff there.\n\n## Security considerations\n\n- When using HTTP, or if your `server.key` has been stolen or broken, an eavesdropper can steal your update key, and use that to steal your domain name.\n- When using HTTPS with `curl -k`, a man-in-the-middle can steal your update key.\n- When using HTTPS with a paid certificate, a man-in-the-middle with access to a CA can steal your update key (no problem for government agencies, but this is pretty unlikely to happen).\n- When using HTTPS with a self-signed certificate and `curl --cacert server.crt`, no man-in-the-middle can steal your update key.\n\n`sftdyn` is pretty minimalistic, and written in python, so it's unlikely to contain any security vulnerabilities. The python ssl and http modules are used widely, and open-source, so there _should_ be no security vulnerabilities there.\n\nSomebody who knows a valid udpate key could semi-effectively DOS your server by spamming update requests from two different IPs. For each request, nsupdate would be launched and your zone file updated.\n\n## Development\n\nFor us, the project is feature-complete, it has everything that **we** currently need.\nIf you actually _did_ implement a useful feature, please send a pull request; We'd be happy to merge it.\n\nIf you have any **requests**, **ideas**, **feedback** or **bug reports**,\nare simply **filled with pure hatred**,\nor just **need help** getting the damn thing to run,\njoin our chatroom and just ask:\n\n- Matrix: [`#sfttech:matrix.org`](https://matrix.to/#/#sfttech:matrix.org)\n\n\nThe license is GNU GPLv3 or higher.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsfttech%2Fsftdyn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsfttech%2Fsftdyn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsfttech%2Fsftdyn/lists"}