-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
David Prandzioch
committed
Dec 18, 2016
1 parent
09dc112
commit 44e7b38
Showing
8 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
FROM debian:jessie | ||
MAINTAINER David Prandzioch <[email protected]> | ||
|
||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ | ||
apt-get install -q -y bind9 dnsutils golang git-core && \ | ||
apt-get clean | ||
|
||
RUN chmod 770 /var/cache/bind | ||
|
||
COPY setup.sh /root/setup.sh | ||
RUN chmod +x /root/setup.sh | ||
|
||
ENV GOPATH=/root/go | ||
RUN mkdir -p /root/go/src | ||
COPY rest-api /root/go/src/dyndns | ||
RUN cd /root/go/src/dyndns && go get | ||
|
||
COPY named.conf.options /etc/bind/named.conf.options | ||
|
||
EXPOSE 53 8080 | ||
CMD ["sh", "-c", "/root/setup.sh ; service bind9 start ; /root/go/bin/dyndns"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
image: | ||
docker build -t davd/dyndns-server . | ||
|
||
console: | ||
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --rm davd/dyndns-server bash | ||
|
||
server_test: | ||
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --rm davd/dyndns-server | ||
|
||
api_test: | ||
curl "http://localhost:8080/update?secret=changeme&domain=foo&addr=1.2.3.4" | ||
dig @localhost foo.example.org | ||
|
||
api_test_recursion: | ||
dig @localhost google.com | ||
|
||
deploy: image | ||
docker run -it -d -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --name=dyndns davd/dyndns-server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Dynamic DNS with Docker, Go and Bind9 | ||
|
||
This package allows you to set up a server for dynamic DNS using docker with a | ||
few simple commands. You don't have to worry about nameserver setup, REST API | ||
and all that stuff. Setup is as easy as that: | ||
|
||
## Installation | ||
|
||
``` | ||
git clone https://github.com/dprandzioch/docker-ddns | ||
cd docker-ddns | ||
$EDITOR envfile | ||
make deploy | ||
``` | ||
|
||
Make sure to change all environment variables in `envfile` to match your needs. | ||
|
||
Afterwards you have a running docker container that exposes three ports: | ||
|
||
* 53/TCP -> DNS | ||
* 53/UDP -> DNS | ||
* 8080/TCP -> Management REST API | ||
|
||
|
||
## Using the API | ||
|
||
That package features a simple REST API written in Go, that provides a simple | ||
interface, that almost any router that supports Custom DDNS providers can | ||
attach to (e.g. Fritz!Box). It is highly recommended to put a reverse proxy | ||
before the API. | ||
|
||
It provides one single GET request, that is used as follows: | ||
|
||
http://myhost.mydomain.tld:8080/update?secret=changeme&domain=foo&addr=1.2.3.4 | ||
|
||
### Fields | ||
|
||
* `secret`: The shared secret set in `envfile` | ||
* `domain`: The subdomain to your configured domain, in this example it would | ||
result in `foo.example.org` | ||
* `addr`: IPv4 or IPv6 address of the name record | ||
|
||
## Accessing the REST API log | ||
|
||
Just run | ||
|
||
``` | ||
docker logs -f dyndns | ||
``` | ||
|
||
## DNS setup | ||
|
||
To provide a little help... To your "real" domain, like `domain.tld`, you | ||
should add a subdomain that is delegated to this DDNS server like this: | ||
|
||
``` | ||
dyndns IN NS ns | ||
ns IN A <put ipv4 of dns server here> | ||
ns IN AAAA <optional, put ipv6 of dns server here> | ||
``` | ||
|
||
Your management API should then also be accessible through | ||
|
||
``` | ||
http://ns.domain.tld:8080/update?... | ||
``` | ||
|
||
If you provide `foo` as a domain when using the REST API, the resulting domain | ||
will then be `foo.dyndns.domain.tld`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
SHARED_SECRET=changeme | ||
ZONE=example.org | ||
RECORD_TTL=3600 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
options { | ||
directory "/var/cache/bind"; | ||
dnssec-validation auto; | ||
recursion no; | ||
allow-transfer { none; }; | ||
auth-nxdomain no; | ||
listen-on-v6 { any; }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
) | ||
|
||
type Config struct { | ||
SharedSecret string | ||
Server string | ||
Zone string | ||
Domain string | ||
NsupdateBinary string | ||
RecordTTL int | ||
} | ||
|
||
func (conf *Config) LoadConfig(path string) { | ||
file, err := os.Open(path) | ||
if err != nil { | ||
panic(err) | ||
} | ||
decoder := json.NewDecoder(file) | ||
err = decoder.Decode(&conf) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package main | ||
|
||
import ( | ||
"log" | ||
"fmt" | ||
"net/http" | ||
"io/ioutil" | ||
"os" | ||
"bufio" | ||
"os/exec" | ||
"bytes" | ||
"encoding/json" | ||
"net" | ||
|
||
"github.com/gorilla/mux" | ||
) | ||
|
||
var appConfig = &Config{} | ||
|
||
type WebserviceResponse struct { | ||
Success bool | ||
Message string | ||
} | ||
|
||
func main() { | ||
appConfig.LoadConfig("/etc/dyndns.json") | ||
|
||
router := mux.NewRouter().StrictSlash(true) | ||
router.HandleFunc("/update", Update).Methods("GET") | ||
|
||
log.Println(fmt.Sprintf("Serving dyndns REST services on 0.0.0.0:8080...")) | ||
log.Fatal(http.ListenAndServe(":8080", router)) | ||
} | ||
|
||
func validIP4(ipAddress string) bool { | ||
testInput := net.ParseIP(ipAddress) | ||
if testInput == nil { | ||
return false | ||
} | ||
|
||
return (testInput.To4() != nil) | ||
} | ||
|
||
func validIP6(ip6Address string) bool { | ||
testInputIP6 := net.ParseIP(ip6Address) | ||
if testInputIP6 == nil { | ||
return false | ||
} | ||
|
||
return (testInputIP6.To16() != nil) | ||
} | ||
|
||
func Update(w http.ResponseWriter, r *http.Request) { | ||
response := WebserviceResponse{} | ||
|
||
var sharedSecret string | ||
var domain string | ||
var address string | ||
|
||
vals := r.URL.Query() | ||
sharedSecret = vals["secret"][0] | ||
domain = vals["domain"][0] | ||
address = vals["addr"][0] | ||
|
||
if sharedSecret != appConfig.SharedSecret { | ||
log.Println(fmt.Sprintf("Invalid shared secret: %s", sharedSecret)) | ||
response.Success = false | ||
response.Message = "Invalid Credentials" | ||
json.NewEncoder(w).Encode(response) | ||
return; | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
|
||
var addrType string | ||
|
||
if validIP4(address) { | ||
addrType = "A" | ||
} else if validIP6(address) { | ||
addrType = "AAAA" | ||
} else { | ||
response.Success = false | ||
response.Message = fmt.Sprintf("%s is neither a valid IPv4 nor IPv6 address", address) | ||
} | ||
|
||
if addrType != "" { | ||
if domain == "" { | ||
response.Success = false | ||
response.Message = fmt.Sprintf("Domain not set", address) | ||
log.Println(fmt.Sprintf("Domain not set")) | ||
return; | ||
} | ||
|
||
result := UpdateRecord(domain, address, addrType) | ||
|
||
if result == "" { | ||
response.Success = true | ||
response.Message = fmt.Sprintf("Updated %s record for %s to IP address %s", addrType, domain, address) | ||
} else { | ||
response.Success = false | ||
response.Message = result | ||
} | ||
} | ||
|
||
json.NewEncoder(w).Encode(response) | ||
} | ||
|
||
func UpdateRecord(domain string, ipaddr string, addrType string) string { | ||
log.Println(fmt.Sprintf("%s record update request: %s -> %s", addrType, domain, ipaddr)) | ||
|
||
f, err := ioutil.TempFile(os.TempDir(), "dyndns") | ||
if err != nil { | ||
return err.Error() | ||
} | ||
|
||
defer os.Remove(f.Name()) | ||
w := bufio.NewWriter(f) | ||
|
||
w.WriteString(fmt.Sprintf("server %s\n", appConfig.Server)) | ||
w.WriteString(fmt.Sprintf("zone %s\n", appConfig.Zone)) | ||
w.WriteString(fmt.Sprintf("update delete %s.%s A\n", domain, appConfig.Domain)) | ||
w.WriteString(fmt.Sprintf("update delete %s.%s AAAA\n", domain, appConfig.Domain)) | ||
w.WriteString(fmt.Sprintf("update add %s.%s %v %s %s\n", domain, appConfig.Domain, appConfig.RecordTTL, addrType, ipaddr)) | ||
w.WriteString("send\n") | ||
|
||
w.Flush() | ||
f.Close() | ||
|
||
cmd := exec.Command(appConfig.NsupdateBinary, f.Name()) | ||
var out bytes.Buffer | ||
var stderr bytes.Buffer | ||
cmd.Stdout = &out | ||
cmd.Stderr = &stderr | ||
err = cmd.Run() | ||
if err != nil { | ||
return err.Error() + ": " + stderr.String() | ||
} | ||
|
||
return out.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#!/bin/bash | ||
|
||
[ -z "$SHARED_SECRET" ] && echo "SHARED_SECRET not set" && exit 1; | ||
[ -z "$ZONE" ] && echo "ZONE not set" && exit 1; | ||
[ -z "$RECORD_TTL" ] && echo "RECORD_TTL not set" && exit 1; | ||
|
||
if [ ! -f /var/cache/bind/$ZONE.zone ] | ||
then | ||
echo "creating zone..."; | ||
cat >> /etc/bind/named.conf <<EOF | ||
zone "$ZONE" { | ||
type master; | ||
file "$ZONE.zone"; | ||
allow-query { any; }; | ||
allow-transfer { none; }; | ||
allow-update { localhost; }; | ||
}; | ||
EOF | ||
|
||
echo "creating zone file..." | ||
cat > /var/cache/bind/$ZONE.zone <<EOF | ||
\$ORIGIN . | ||
\$TTL 86400 ; 1 day | ||
$ZONE IN SOA localhost. root.localhost. ( | ||
74 ; serial | ||
3600 ; refresh (1 hour) | ||
900 ; retry (15 minutes) | ||
604800 ; expire (1 week) | ||
86400 ; minimum (1 day) | ||
) | ||
NS localhost. | ||
\$ORIGIN ${ZONE}. | ||
\$TTL ${RECORD_TTL} | ||
EOF | ||
fi | ||
|
||
if [ ! -f /etc/dyndns.json ] | ||
then | ||
echo "creating REST api config..." | ||
cat > /etc/dyndns.json <<EOF | ||
{ | ||
"SharedSecret": "${SHARED_SECRET}", | ||
"Server": "localhost", | ||
"Zone": "${ZONE}.", | ||
"Domain": "${ZONE}", | ||
"NsupdateBinary": "/usr/bin/nsupdate", | ||
"RecordTTL": ${RECORD_TTL} | ||
} | ||
EOF | ||
fi |