Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
David Prandzioch committed Nov 12, 2017
2 parents 19f083b + df7caca commit 64d3839
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 83 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[1.1.0]
* Update Debian Jessie to Debian Stretch
* Multistage Dockerfile resulting in smaller production image
* Code refactoring
* Extended response
* Basic unit test coverage
* Documentation on running from DockerHub

[1.0.0]
* Initial release
26 changes: 15 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
FROM debian:jessie
MAINTAINER David Prandzioch <[email protected]>

FROM debian:stretch as builder
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -q -y bind9 dnsutils golang git-core && \
apt-get install -q -y 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
RUN cd /root/go/src/dyndns && go get && go test -v

FROM debian:stretch
MAINTAINER David Prandzioch <[email protected]>

RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -q -y bind9 dnsutils && \
apt-get clean

RUN chmod 770 /var/cache/bind
COPY setup.sh /root/setup.sh
RUN chmod +x /root/setup.sh
COPY named.conf.options /etc/bind/named.conf.options
COPY --from=builder /root/go/bin/dyndns /root/dyndns

EXPOSE 53 8080
CMD ["sh", "-c", "/root/setup.sh ; service bind9 start ; /root/go/bin/dyndns"]
CMD ["sh", "-c", "/root/setup.sh ; service bind9 start ; /root/dyndns"]
24 changes: 17 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
image:
docker build -t davd/dyndns-server .
docker build -t davd/docker-ddns:latest .

console:
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --rm davd/dyndns-server bash
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --rm davd/docker-ddns:latest bash

devconsole:
docker run -it --rm -v ${PWD}/rest-api:/usr/src/app -w /usr/src/app golang:1.8.5 bash

server_test:
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --rm davd/dyndns-server
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --rm davd/docker-ddns:latest

unit_tests:
docker run -it --rm -v ${PWD}/rest-api:/go/src/dyndns -w /go/src/dyndns golang:1.8.5 /bin/bash -c "go get && go test -v"

api_test:
curl "http://localhost:8080/update?secret=changeme&domain=foo&addr=1.2.3.4"
dig @localhost foo.example.org
curl "http://docker.local:8080/update?secret=changeme&domain=foo&addr=1.2.3.4"
dig @docker.local foo.example.org

api_test_invalid_params:
curl "http://docker.local:8080/update?secret=changeme&addr=1.2.3.4"
dig @docker.local foo.example.org

api_test_recursion:
dig @localhost google.com
dig @docker.local 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
docker run -it -d -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --name=dyndns davd/docker-ddns:latest
24 changes: 24 additions & 0 deletions rest-api/ipparser/ipparser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ipparser

import (
"net"
)

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)
}

30 changes: 30 additions & 0 deletions rest-api/ipparser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"testing"
"dyndns/ipparser"
)

func TestValidIP4ToReturnTrueOnValidAddress(t *testing.T) {
result := ipparser.ValidIP4("1.2.3.4")

if result != true {
t.Fatalf("Expected ValidIP(1.2.3.4) to be true but got false")
}
}

func TestValidIP4ToReturnFalseOnInvalidAddress(t *testing.T) {
result := ipparser.ValidIP4("abcd")

if result == true {
t.Fatalf("Expected ValidIP(abcd) to be false but got true")
}
}

func TestValidIP4ToReturnFalseOnEmptyAddress(t *testing.T) {
result := ipparser.ValidIP4("")

if result == true {
t.Fatalf("Expected ValidIP() to be false but got true")
}
}
69 changes: 4 additions & 65 deletions rest-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,12 @@ import (
"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")

Expand All @@ -32,70 +26,15 @@ func main() {
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;
}
response := BuildWebserviceResponseFromRequest(r, appConfig)

result := UpdateRecord(domain, address, addrType)
if response.Success {
result := UpdateRecord(response.Domain, response.Address, response.AddrType)

if result == "" {
response.Success = true
response.Message = fmt.Sprintf("Updated %s record for %s to IP address %s", addrType, domain, address)
response.Message = fmt.Sprintf("Updated %s record for %s to IP address %s", response.AddrType, response.Domain, response.Address)
} else {
response.Success = false
response.Message = result
Expand Down
57 changes: 57 additions & 0 deletions rest-api/request_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"log"
"fmt"
"net/http"

"dyndns/ipparser"
)

type WebserviceResponse struct {
Success bool
Message string
Domain string
Address string
AddrType string
}

func BuildWebserviceResponseFromRequest(r *http.Request, appConfig *Config) WebserviceResponse {
response := WebserviceResponse{}

var sharedSecret string

vals := r.URL.Query()
sharedSecret = vals.Get("secret")
response.Domain = vals.Get("domain")
response.Address = vals.Get("addr")

if sharedSecret != appConfig.SharedSecret {
log.Println(fmt.Sprintf("Invalid shared secret: %s", sharedSecret))
response.Success = false
response.Message = "Invalid Credentials"
return response
}

if response.Domain == "" {
response.Success = false
response.Message = fmt.Sprintf("Domain not set")
log.Println("Domain not set")
return response
}

if ipparser.ValidIP4(response.Address) {
response.AddrType = "A"
} else if ipparser.ValidIP6(response.Address) {
response.AddrType = "AAAA"
} else {
response.Success = false
response.Message = fmt.Sprintf("%s is neither a valid IPv4 nor IPv6 address", response.Address)
log.Println(fmt.Sprintf("Invalid address: %s", response.Address))
return response
}

response.Success = true

return response
}
90 changes: 90 additions & 0 deletions rest-api/request_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"testing"
"net/http"
)

func TestBuildWebserviceResponseFromRequestToReturnValidObject(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo&addr=1.2.3.4", nil)
result := BuildWebserviceResponseFromRequest(req, appConfig)

if result.Success != true {
t.Fatalf("Expected WebserviceResponse.Success to be true")
}

if result.Domain != "foo" {
t.Fatalf("Expected WebserviceResponse.Domain to be foo")
}

if result.Address != "1.2.3.4" {
t.Fatalf("Expected WebserviceResponse.Address to be 1.2.3.4")
}

if result.AddrType != "A" {
t.Fatalf("Expected WebserviceResponse.AddrType to be A")
}
}

func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoSecretIsGiven(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("POST", "/update", nil)
result := BuildWebserviceResponseFromRequest(req, appConfig)

if result.Success != false {
t.Fatalf("Expected WebserviceResponse.Success to be false")
}
}

func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidSecretIsGiven(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("POST", "/update?secret=foo", nil)
result := BuildWebserviceResponseFromRequest(req, appConfig)

if result.Success != false {
t.Fatalf("Expected WebserviceResponse.Success to be false")
}
}

func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoDomainIsGiven(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("POST", "/update?secret=changeme", nil)
result := BuildWebserviceResponseFromRequest(req, appConfig)

if result.Success != false {
t.Fatalf("Expected WebserviceResponse.Success to be false")
}
}

func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoAddressIsGiven(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo", nil)
result := BuildWebserviceResponseFromRequest(req, appConfig)

if result.Success != false {
t.Fatalf("Expected WebserviceResponse.Success to be false")
}
}

func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidAddressIsGiven(t *testing.T) {
var appConfig = &Config{}
appConfig.SharedSecret = "changeme"

req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo&addr=1.41:2", nil)
result := BuildWebserviceResponseFromRequest(req, appConfig)

if result.Success != false {
t.Fatalf("Expected WebserviceResponse.Success to be false")
}
}

0 comments on commit 64d3839

Please sign in to comment.