mirror of
https://github.com/maxkratz/coredns-dyndns.git
synced 2024-05-18 04:00:47 +00:00
Compare commits
2 commits
056076e0c0
...
bb49ff0339
Author | SHA1 | Date | |
---|---|---|---|
Maximilian Kratz | bb49ff0339 | ||
Maximilian Kratz | 5a666b3469 |
45
README.md
45
README.md
|
@ -1,16 +1,23 @@
|
|||
# CoreDNS-DynDNS
|
||||
|
||||
This repository holds some basic configurations to enable dynamic DNS updates in an authorative DNS-Server running [CoreDNS](https://github.com/coredns/coredns) without implementing a custom plugin.
|
||||
Therefore, this repsoitory "simulates" a [DynDNSv2](https://stackoverflow.com/questions/54039095/dyndns2-protocol-specification) endpoint.
|
||||
Therefore, this repsoitory "simulates" a [DynDNSv2](https://stackoverflow.com/questions/54039095/dyndns2-protocol-specification) endpoint.
|
||||
Available functions:
|
||||
- Change an `A` record via DynDNSv2.
|
||||
- Update a `TXT` record for the [Let's Encrypt DNS challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) via a [Cerbot](https://certbot.eff.org/) script.
|
||||
|
||||
|
||||
Keep in mind that this is kind of a blueprint and not necessary production-ready.
|
||||
|
||||
|
||||
## How does it work?
|
||||
## DynDNS A record update
|
||||
|
||||
### How does it work?
|
||||
|
||||
CoreDNS is able to reread configuration and zone files on changes.
|
||||
This project provides a quite simple way to update such a zone file via a [webhook](https://github.com/adnanh/webhook).
|
||||
Most *decent* routers allow to specify a custom URL with basic auth to send IP updates to.
|
||||
The webhook provides such a URL and triggers a sh script that updates the dynamic zone file and boom - your dynamic IP updates are done.
|
||||
The webhook provides such a URL and triggers an sh script that updates the dynamic zone file and boom - your dynamic IP updates are done.
|
||||
|
||||
**In short (step-by-step):**
|
||||
1. The client receives a new dynamic IP address.
|
||||
|
@ -24,7 +31,7 @@ The sh script checks if the provided parameter is a valid IPv4 address using a r
|
|||
Currently, the updating of IPv6 addresses is **not** supported.
|
||||
|
||||
|
||||
## Configuration
|
||||
### Configuration
|
||||
|
||||
A [Docker-Compose](https://docs.docker.com/compose/) stack is used for the example configuration.
|
||||
Feel free to adapt the needed steps to, e.g., a native installation (with binaries) (although Docker-Compose is running just fine for my personal setup).
|
||||
|
@ -46,7 +53,7 @@ Hint: Mostly, you want to use the stack behind a reverse proxy such as nginx sec
|
|||
For an easy to use reverse proxy for Docker containers, check out [nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) with its [config examples](https://github.com/nginx-proxy/acme-companion/blob/main/docs/Docker-Compose.md).
|
||||
|
||||
|
||||
### OPNsense client example
|
||||
#### OPNsense client example
|
||||
|
||||
This example can easily be used to update a dynamic zone with your [OPNsense](https://opnsense.org/) router.
|
||||
|
||||
|
@ -66,3 +73,31 @@ Configure the following settings:
|
|||
| *Force SSL* | Checked | Uses HTTPS instead of HTTP |
|
||||
| *Interface to monitor* | E.g., *WAN* | Interface on which the dynamic IP occurs |
|
||||
| *Description* | | - |
|
||||
|
||||
|
||||
## Let's Encrypt DNS challenge
|
||||
|
||||
Additionally to the previous mentioned updating of A records, this stack also provides a function to update a TXT record for a Let's Encrypt DNS challenge.
|
||||
This means you can use it to include a TXT record that Certbot needs to update a Let's Encrypt certificate on a server that may not have a publicly available web server on port 80 + 443.
|
||||
|
||||
### How does it work?
|
||||
|
||||
The core information is explained in the section of updating A records above.
|
||||
Let's Encrypt steps:
|
||||
1. Certbot requests a new certificate. The Let's Encrypt servers request to update the TXT record with value "ABC".
|
||||
1. Certbot triggers the [le.sh](./scripts/certbot/le.sh) script with value "ABC".
|
||||
1. The script sends a webhook with parameter "ABC" to http://$nameserver/nic/le
|
||||
1. The script [cng.sh](./scripts/cng.sh) updates the dynamic zone file (including "ABC" as TXT record), updates the serial, and saves it to disk.
|
||||
1. CoreDNS automatically reloads the zone file and serves the new IP address.
|
||||
1. [le.sh](./scripts/certbot/le.sh) waits 60s to ensure the updated zone is available.
|
||||
1. The Let's Encrypt servers can now validate the TXT record and give the new certificate to Certbot.
|
||||
|
||||
**Relevant files**:
|
||||
| File | Purpose |
|
||||
| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| [le.sh](./scripts/certbot/le.sh) | Certbot script that can provide the necessary call to this stack. |
|
||||
| [cng.sh](./scripts/cng.sh) | Script that updates the TXT record within the dynamic zone file (analogously to the A record script explained above). |
|
||||
| [htpasswd_le](./config/dynamic/htpasswd_le) | Contains the authentification user/pw combination for the `cng.sh` script. |
|
||||
| [db.example.com.dyn.template](./zones/example.com/db.example.com.dyn.template) | Contains the necessary A record as well as the TXT record to update. |
|
||||
| [default.conf](./config/dynamic/default.conf) | nginx configuration to pass calls to respective webhooks. |
|
||||
| [webhook.json](./config/dynamic/webhook.json) | Contains the webhook configuration. |
|
||||
|
|
|
@ -9,6 +9,12 @@ server {
|
|||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location /nic/le {
|
||||
auth_basic "Restricted API";
|
||||
auth_basic_user_file /etc/nginx/conf.d/htpasswd_le;
|
||||
proxy_pass http://webhook/hooks/le;
|
||||
}
|
||||
|
||||
location /nic {
|
||||
auth_basic "Restricted API";
|
||||
auth_basic_user_file /etc/nginx/conf.d/htpasswd;
|
||||
|
|
1
config/dynamic/htpasswd_le
Normal file
1
config/dynamic/htpasswd_le
Normal file
|
@ -0,0 +1 @@
|
|||
example:$1$Qbub^M\U$3601.7bQwZws2mUa7RHWf1
|
|
@ -14,5 +14,17 @@
|
|||
"name": "hostname"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "le",
|
||||
"execute-command": "/app/cng.sh",
|
||||
"include-command-output-in-response": true,
|
||||
"pass-arguments-to-command":
|
||||
[
|
||||
{
|
||||
"source": "query",
|
||||
"name": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -3,7 +3,7 @@ $TTL 3600
|
|||
|
||||
; SOA Record
|
||||
@ 3600 IN SOA ns1.example.com. hostmaster.example.com. (
|
||||
2021012220 ; serial
|
||||
2021012221 ; serial
|
||||
7200 ; refresh (7200 = 2 hours)
|
||||
3600 ; retry (3600 = 1 hour)
|
||||
1209600 ; expire (1209600 = 2 weeks)
|
||||
|
@ -21,3 +21,7 @@ dyn 3600 IN NS ns3.example.com.
|
|||
; A / AAAA Records
|
||||
* IN A 192.0.2.1
|
||||
@ IN A 192.0.2.1
|
||||
|
||||
; Lets Encrypt challenge
|
||||
;_acme-challenge IN TXT "a"
|
||||
_acme-challenge IN CNAME _acme-challenge.dyn.example.com.
|
||||
|
|
|
@ -3,7 +3,7 @@ $TTL 300
|
|||
|
||||
; SOA Record
|
||||
@ 3600 IN SOA ns1.example.com. hostmaster.example.com. (
|
||||
2021012220; serial
|
||||
2021012221; serial
|
||||
7200 ; refresh (7200 = 2 hours)
|
||||
3600 ; retry (3600 = 1 hour)
|
||||
1209600 ; expire (1209600 = 2 weeks)
|
||||
|
@ -17,3 +17,6 @@ $TTL 300
|
|||
|
||||
; A / AAAA Records
|
||||
@ IN A 192.0.2.2
|
||||
|
||||
; Lets Encrypt challenge
|
||||
_acme-challenge IN TXT "a"
|
||||
|
|
|
@ -23,6 +23,7 @@ services:
|
|||
volumes:
|
||||
- './config/dynamic/default.conf:/etc/nginx/conf.d/default.conf:ro'
|
||||
- './config/dynamic/htpasswd:/etc/nginx/conf.d/htpasswd:ro'
|
||||
- './config/dynamic/htpasswd_le:/etc/nginx/conf.d/htpasswd_le:ro'
|
||||
|
||||
# webhook that triggers ip address update
|
||||
webhook:
|
||||
|
@ -32,4 +33,5 @@ services:
|
|||
volumes:
|
||||
- './config/dynamic/webhook.json:/etc/webhook/hooks.json:ro'
|
||||
- './scripts/dyn.sh:/app/dyn.sh:ro'
|
||||
- './scripts/cng.sh:/app/cng.sh:ro'
|
||||
- './config/zones/example.com/db.example.com.dyn:/zonefile'
|
||||
|
|
14
scripts/certbot/le.sh
Executable file
14
scripts/certbot/le.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Config
|
||||
USER="TODO"
|
||||
PW="TODO"
|
||||
HOST="http://dyn.ns1.example.com"
|
||||
|
||||
# Hook
|
||||
curl -u "$USER:$PW" $HOST/nic/le\?value\="$CERTBOT_VALIDATION"
|
||||
|
||||
# Wait to let DNS propagate
|
||||
sleep 60
|
43
scripts/cng.sh
Executable file
43
scripts/cng.sh
Executable file
|
@ -0,0 +1,43 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Get VALUE from parameter
|
||||
VAL=$1
|
||||
|
||||
# Check if provided IP parameter was empty
|
||||
if [ -z "$VAL" ]
|
||||
then
|
||||
echo "badsys"
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
# Get previous TXT from zone file
|
||||
TXT_OLD=$(grep '_acme-challenge IN TXT "' /zonefile_le | cut -d\ -f4)
|
||||
|
||||
# Replace old val from zonefile by new val, use tee instead of sed -i
|
||||
# because Docker does not permit the overwriting of inodes.
|
||||
sed "s/_acme-challenge IN TXT \".*/_acme-challenge IN TXT \"${VAL}\"/" /zonefile_le > /zonefile_le.tmp
|
||||
|
||||
# Get old serial from zonefile and set value to current date/time stamp
|
||||
SERIAL_OLD=$(cat /zonefile_le.tmp | grep "; serial" | tr -dc '0-9')
|
||||
SERIAL_NEW=$(date +%y%m%d%H%M)
|
||||
|
||||
# Ensure that new serial is always larger than previous one
|
||||
# (This could happen if there is more than one update per minute)
|
||||
if [ "$SERIAL_OLD" -ge "$SERIAL_NEW" ]; then
|
||||
SERIAL_NEW=$((SERIAL_OLD+1))
|
||||
fi
|
||||
|
||||
# Replace old serial with new value in temp zonefile
|
||||
sed -i "s/$SERIAL_OLD/$SERIAL_NEW/" /zonefile_le.tmp
|
||||
|
||||
# Overwrite old zonefile by temp zonefile
|
||||
cat /zonefile_le.tmp > /zonefile_le
|
||||
|
||||
if [[ "$TXT_OLD" == "$VAL" ]]
|
||||
then
|
||||
echo "nochg"
|
||||
else
|
||||
echo "good"
|
||||
fi
|
Loading…
Reference in a new issue