Skip to content

Commit 00fba17

Browse files
committed
Change sharedSecred checking method
The rest-api does not need to check the shared secret because bind itself can check it. This change also allows to have different shared secrets for different zones. See dstapp#55
1 parent eb88b8b commit 00fba17

File tree

8 files changed

+56
-69
lines changed

8 files changed

+56
-69
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ FROM debian:buster-slim
99
MAINTAINER David Prandzioch <[email protected]>
1010

1111
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
12-
apt-get install -q -y bind9 dnsutils && \
12+
apt-get install -q -y bind9 dnsutils openssl && \
1313
apt-get clean
1414

1515
RUN chmod 770 /var/cache/bind

rest-api/config.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ import (
77
)
88

99
type Config struct {
10-
SharedSecret string
1110
Server string
1211
Zone string
13-
Domain string
1412
NsupdateBinary string
1513
RecordTTL int
1614
Port int
@@ -38,10 +36,8 @@ func (conf *Config) loadConfigFromFile(path string) {
3836
func (flagsConf *ConfigFlags) setupFlags() {
3937
flag.BoolVar(&flagsConf.DoNotLoadConfig, "noConfig", false, "Do not load the config file")
4038
flag.StringVar(&flagsConf.ConfigFile, "c", "/etc/dyndns.json", "The configuration file")
41-
flag.StringVar(&flagsConf.SharedSecret, "sharedSecret", "", "The shared secret (default a generated random string)")
4239
flag.StringVar(&flagsConf.Server, "server", "localhost", "The address of the bind server")
4340
flag.StringVar(&flagsConf.Zone, "zone", "localhost", "Zone")
44-
flag.StringVar(&flagsConf.Domain, "domain", "localhost", "Domain")
4541
flag.StringVar(&flagsConf.NsupdateBinary, "nsupdateBinary", "nsupdate", "Path to nsupdate program")
4642
flag.IntVar(&flagsConf.RecordTTL, "recordTTL", 300, "RecordTTL")
4743
flag.IntVar(&flagsConf.Port, "p", 8080, "Port")
@@ -58,9 +54,4 @@ func (flagsConf *ConfigFlags) LoadConfig() {
5854
flagsConf.loadConfigFromFile(flagsConf.ConfigFile)
5955
flag.Parse() // Parse a second time to overwrite settings from the loaded file
6056
}
61-
62-
// Fix unsafe config values
63-
if flagsConf.SharedSecret == "" {
64-
flagsConf.SharedSecret = randomString()
65-
}
6657
}

rest-api/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ func updateDomains(r *http.Request, response *WebserviceResponse, onError func()
8686
for _, address := range response.Addresses {
8787
for _, domain := range response.Domains {
8888
recordUpdate := RecordUpdateRequest{
89-
domain: domain,
90-
ipaddr: address.Address,
91-
addrType: address.AddrType,
92-
ddnskey: extractor.DdnsKey(r),
89+
domain: domain,
90+
ipAddr: address.Address,
91+
addrType: address.AddrType,
92+
secret: extractor.Secret(r),
9393
}
9494
result := recordUpdate.updateRecord()
9595

@@ -122,7 +122,7 @@ func (r RecordUpdateRequest) updateRecord() string {
122122
status = "failed, error: " + result
123123
}
124124

125-
log.Println(fmt.Sprintf("%s record update request: %s -> %s %s", r.addrType, r.domain, r.ipaddr, status))
125+
log.Println(fmt.Sprintf("%s record update request: %s -> %s %s", r.addrType, r.domain, r.ipAddr, status))
126126

127127
return result
128128
}

rest-api/nsupdate.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ type NSUpdateInterface interface {
1919

2020
// RecordUpdateRequest data representing a update request
2121
type RecordUpdateRequest struct {
22-
domain string
23-
ipaddr string
24-
addrType string
25-
ddnskey string
22+
domain string
23+
ipAddr string
24+
addrType string
25+
ddnsKeyName string
26+
secret string
27+
zone string
28+
fqdn string
2629
}
2730

2831
// NSUpdate holds resources need for an open nsupdate program
@@ -106,16 +109,16 @@ func (nsupdate *NSUpdate) UpdateRecord(r RecordUpdateRequest) {
106109
fqdn = escape(r.domain) + "." + appConfig.Zone
107110
}
108111

109-
if r.ddnskey != "" {
110-
fqdnN := strings.TrimLeft(fqdn, ".")
111-
nsupdate.write("key hmac-sha256:ddns-key.%s %s\n", fqdnN, escape(r.ddnskey))
112+
if r.secret != "" {
113+
fqdnN := strings.TrimLeft(appConfig.Zone, ".")
114+
nsupdate.write("key hmac-sha256:ddns-key.%s %s\n", fqdnN, escape(r.secret))
112115
}
113116

114117
nsupdate.write("server %s\n", appConfig.Server)
115118
if appConfig.Zone != "" {
116119
nsupdate.write("zone %s\n", appConfig.Zone)
117120
}
118121
nsupdate.write("update delete %s %s\n", fqdn, r.addrType)
119-
nsupdate.write("update add %s %v %s %s\n", fqdn, appConfig.RecordTTL, r.addrType, escape(r.ipaddr))
122+
nsupdate.write("update add %s %v %s %s\n", fqdn, appConfig.RecordTTL, r.addrType, escape(r.ipAddr))
120123
nsupdate.write("send\n")
121124
}

rest-api/request_handler.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ func ParseAddress(address string) (Address, error) {
3939
func BuildWebserviceResponseFromRequest(r *http.Request, appConfig *Config, extractors requestDataExtractor) WebserviceResponse {
4040
response := WebserviceResponse{}
4141

42-
sharedSecret := extractors.Secret(r)
4342
response.Domains = strings.Split(extractors.Domain(r), ",")
4443
for _, address := range strings.Split(extractors.Address(r), ",") {
4544
var parsedAddress, error = ParseAddress(address)
@@ -48,8 +47,8 @@ func BuildWebserviceResponseFromRequest(r *http.Request, appConfig *Config, extr
4847
}
4948
}
5049

51-
if sharedSecret != appConfig.SharedSecret {
52-
log.Println(fmt.Sprintf("Invalid shared secret: %s", sharedSecret))
50+
if extractors.Secret(r) == "" { // futher checking is done by bind server as configured
51+
log.Println(fmt.Sprintf("Invalid shared secret"))
5352
response.Success = false
5453
response.Message = "Invalid Credentials"
5554
return response

rest-api/request_handler_test.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ var dynExtractor = dynRequestDataExtractor{}
1111

1212
func TestBuildWebserviceResponseFromRequestToReturnValidObject(t *testing.T) {
1313
var appConfig = &Config{}
14-
appConfig.SharedSecret = "changeme"
1514

1615
req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo&addr=1.2.3.4", nil)
1716
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -35,7 +34,6 @@ func TestBuildWebserviceResponseFromRequestToReturnValidObject(t *testing.T) {
3534

3635
func TestBuildWebserviceResponseFromRequestWithXRealIPHeaderToReturnValidObject(t *testing.T) {
3736
var appConfig = &Config{}
38-
appConfig.SharedSecret = "changeme"
3937

4038
req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo", nil)
4139
req.Header.Add("X-Real-Ip", "1.2.3.4")
@@ -60,7 +58,6 @@ func TestBuildWebserviceResponseFromRequestWithXRealIPHeaderToReturnValidObject(
6058

6159
func TestBuildWebserviceResponseFromRequestWithXForwardedForHeaderToReturnValidObject(t *testing.T) {
6260
var appConfig = &Config{}
63-
appConfig.SharedSecret = "changeme"
6461

6562
req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo", nil)
6663
req.Header.Add("X-Forwarded-For", "1.2.3.4")
@@ -85,7 +82,6 @@ func TestBuildWebserviceResponseFromRequestWithXForwardedForHeaderToReturnValidO
8582

8683
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoSecretIsGiven(t *testing.T) {
8784
var appConfig = &Config{}
88-
appConfig.SharedSecret = "changeme"
8985

9086
req, _ := http.NewRequest("GET", "/update", nil)
9187
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -97,7 +93,6 @@ func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoSecretIsGi
9793

9894
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidSecretIsGiven(t *testing.T) {
9995
var appConfig = &Config{}
100-
appConfig.SharedSecret = "changeme"
10196

10297
req, _ := http.NewRequest("GET", "/update?secret=foo", nil)
10398
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -109,7 +104,6 @@ func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidSecre
109104

110105
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoDomainIsGiven(t *testing.T) {
111106
var appConfig = &Config{}
112-
appConfig.SharedSecret = "changeme"
113107

114108
req, _ := http.NewRequest("GET", "/update?secret=changeme", nil)
115109
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -121,7 +115,6 @@ func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoDomainIsGi
121115

122116
func TestBuildWebserviceResponseFromRequestWithMultipleDomains(t *testing.T) {
123117
var appConfig = &Config{}
124-
appConfig.SharedSecret = "changeme"
125118

126119
req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo,bar&addr=1.2.3.4", nil)
127120
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -145,7 +138,6 @@ func TestBuildWebserviceResponseFromRequestWithMultipleDomains(t *testing.T) {
145138

146139
func TestBuildWebserviceResponseFromRequestWithMalformedMultipleDomains(t *testing.T) {
147140
var appConfig = &Config{}
148-
appConfig.SharedSecret = "changeme"
149141

150142
req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo,&addr=1.2.3.4", nil)
151143
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -157,7 +149,6 @@ func TestBuildWebserviceResponseFromRequestWithMalformedMultipleDomains(t *testi
157149

158150
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoAddressIsGiven(t *testing.T) {
159151
var appConfig = &Config{}
160-
appConfig.SharedSecret = "changeme"
161152

162153
req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo", nil)
163154
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -169,7 +160,6 @@ func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoAddressIsG
169160

170161
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidAddressIsGiven(t *testing.T) {
171162
var appConfig = &Config{}
172-
appConfig.SharedSecret = "changeme"
173163

174164
req, _ := http.NewRequest("GET", "/update?secret=changeme&domain=foo&addr=1.41:2", nil)
175165
result := BuildWebserviceResponseFromRequest(req, appConfig, defaultExtractor)
@@ -181,7 +171,6 @@ func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidAddre
181171

182172
func TestBuildWebserviceResponseFromRequestToReturnValidObjectWithDynExtractor(t *testing.T) {
183173
var appConfig = &Config{}
184-
appConfig.SharedSecret = "changeme"
185174

186175
req, _ := http.NewRequest("GET", "/nic/update?hostname=foo&myip=1.2.3.4", nil)
187176
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("username:changeme")))
@@ -207,7 +196,6 @@ func TestBuildWebserviceResponseFromRequestToReturnValidObjectWithDynExtractor(t
207196

208197
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoSecretIsGivenWithDynExtractor(t *testing.T) {
209198
var appConfig = &Config{}
210-
appConfig.SharedSecret = "changeme"
211199

212200
req, _ := http.NewRequest("GET", "/nic/update", nil)
213201
result := BuildWebserviceResponseFromRequest(req, appConfig, dynExtractor)

rest-api/utils.go

Lines changed: 0 additions & 17 deletions
This file was deleted.

setup.sh

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,66 @@
11
#!/bin/bash
2+
NAMED_HOST=${NAMED_HOST:-'localhost'}
3+
ZONES=$(echo $ZONE | tr ',' '\n')
4+
RECORD_TTL=${RECORD_TTL:-300}
25

3-
[ -z "$SHARED_SECRET" ] && echo "SHARED_SECRET not set" && exit 1;
4-
[ -z "$ZONE" ] && echo "ZONE not set" && exit 1;
5-
[ -z "$RECORD_TTL" ] && echo "RECORD_TTL not set" && exit 1;
6+
# Backward compatibility for a single zone
7+
if [ $(echo "$ZONES" | wc -l) -eq 1 ]; then
8+
# Allow update without fqdn
9+
ZONE=$(echo "$ZONES" | head -1)
10+
else
11+
# Allow multiple zones and disable updates without fqdn
12+
ZONE=""
13+
fi
14+
15+
# replaces a config value
16+
function bind-conf-set {
17+
local KEY=${1:?'No key set'}
18+
local SECRED=${2:-$(openssl rand 32 | base64)}
19+
sed -E 's@('"${KEY}"')(\W+)"(.*)"@\1 "'${SECRED}'"@g' /dev/stdin
20+
}
621

22+
function bind-zone-add {
23+
local ZONE=${1:?'No zone set'}
724
if ! grep 'zone "'$ZONE'"' /etc/bind/named.conf > /dev/null
825
then
9-
echo "creating zone...";
26+
echo "creating zone for $ZONE...";
1027
cat >> /etc/bind/named.conf <<EOF
28+
include "/etc/bind/ddns/$ZONE.key";
1129
zone "$ZONE" {
1230
type master;
1331
file "$ZONE.zone";
1432
allow-query { any; };
1533
allow-transfer { none; };
16-
allow-update { localhost; };
34+
update-policy { grant ddns-key.$ZONE zonesub ANY; };
1735
};
1836
EOF
1937
fi
2038

2139
if [ ! -f /var/cache/bind/$ZONE.zone ]
2240
then
23-
echo "creating zone file..."
41+
echo "creating zone file for $ZONE..."
2442
cat > /var/cache/bind/$ZONE.zone <<EOF
25-
\$ORIGIN .
43+
\$ORIGIN ${ZONE}.
2644
\$TTL 86400 ; 1 day
27-
$ZONE IN SOA localhost. root.localhost. (
45+
@ IN SOA ns postmaster (
2846
74 ; serial
2947
3600 ; refresh (1 hour)
3048
900 ; retry (15 minutes)
3149
604800 ; expire (1 week)
3250
86400 ; minimum (1 day)
3351
)
34-
NS localhost.
35-
\$ORIGIN ${ZONE}.
36-
\$TTL ${RECORD_TTL}
52+
NS ns
53+
ns A 127.0.0.1
3754
EOF
3855
fi
56+
}
57+
58+
install -d -o bind -g bind /etc/bind/ddns
59+
60+
for Z in $ZONES; do
61+
ddns-confgen -q -z $Z | bind-conf-set secret "${SHARED_SECRET}" | tee /etc/bind/ddns/$Z.key
62+
bind-zone-add $Z
63+
done
3964

4065
# If /var/cache/bind is a volume, permissions are probably not ok
4166
chown root:bind /var/cache/bind
@@ -48,10 +73,8 @@ then
4873
echo "creating REST api config..."
4974
cat > /etc/dyndns.json <<EOF
5075
{
51-
"SharedSecret": "${SHARED_SECRET}",
52-
"Server": "localhost",
76+
"Server": "${NAMED_HOST}",
5377
"Zone": "${ZONE}.",
54-
"Domain": "${ZONE}",
5578
"NsupdateBinary": "/usr/bin/nsupdate",
5679
"RecordTTL": ${RECORD_TTL}
5780
}

0 commit comments

Comments
 (0)