summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2020-01-04 00:28:25 +0000
committerMike Crute <mike@crute.us>2020-01-04 00:28:25 +0000
commitb4214b63d7c73cb0fec55b4e678a98327a159d48 (patch)
treebb5b160d62b25f483151958f498aac81f51898db
parent1a33a24e60ef985db446deb46b193ab57781ea0e (diff)
downloadgo_ddns_manager-b4214b63d7c73cb0fec55b4e678a98327a159d48.tar.bz2
go_ddns_manager-b4214b63d7c73cb0fec55b4e678a98327a159d48.tar.xz
go_ddns_manager-b4214b63d7c73cb0fec55b4e678a98327a159d48.zip
Refactor out of main a bit
-rw-r--r--bind/config.go19
-rw-r--r--go.mod1
-rw-r--r--go.sum5
-rw-r--r--main.go177
-rw-r--r--web.go26
5 files changed, 141 insertions, 87 deletions
diff --git a/bind/config.go b/bind/config.go
index 584b1ee..42b97cf 100644
--- a/bind/config.go
+++ b/bind/config.go
@@ -54,6 +54,25 @@ func (c *BINDConfig) Zone(view, name string) *Zone {
54 return z 54 return z
55} 55}
56 56
57// Find the closest zone that we manage by striping dotted components off the
58// front of the domain until one matches. If there is a match return the zone
59// that matched and any prefix components, if any, as a dotted string. If none
60// match then return nil.
61func (c *BINDConfig) FindClosestZone(zoneIn, view string) (*Zone, string) {
62 suffix := ""
63 prefix := []string{}
64
65 zc := strings.Split(zoneIn, ".")
66 for i := 0; i <= len(zc)-2; i++ {
67 prefix, suffix = zc[:i], strings.Join(zc[i:], ".")
68 if zone := c.Zone(view, suffix); zone != nil {
69 return zone, strings.Join(prefix, ".")
70 }
71 }
72
73 return nil, ""
74}
75
57func (c *BINDConfig) AddKey(k *Key) { 76func (c *BINDConfig) AddKey(k *Key) {
58 c.keys[k.Name] = k 77 c.keys[k.Name] = k
59} 78}
diff --git a/go.mod b/go.mod
index 1fd977c..b8f5134 100644
--- a/go.mod
+++ b/go.mod
@@ -5,4 +5,5 @@ go 1.13
5require ( 5require (
6 github.com/gin-gonic/gin v1.5.0 6 github.com/gin-gonic/gin v1.5.0
7 github.com/miekg/dns v1.1.26 7 github.com/miekg/dns v1.1.26
8 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
8) 9)
diff --git a/go.sum b/go.sum
index 1f2c074..2f5ba2a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,5 @@
1github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 1github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 4github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
4github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 5github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@@ -20,9 +21,11 @@ github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
20github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 21github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
21github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 22github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
22github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 23github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
24github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
23github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
24github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
25github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 27github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
28github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
26github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 29github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
27github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 30github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
28github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 31github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -44,6 +47,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPT
44golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
45golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
46golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 49golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
50golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
51golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
47golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 52golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
48golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 53golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
49golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/main.go b/main.go
index 269b11e..ac9b849 100644
--- a/main.go
+++ b/main.go
@@ -3,12 +3,10 @@ package main
3import ( 3import (
4 "encoding/base64" 4 "encoding/base64"
5 "encoding/json" 5 "encoding/json"
6 "fmt"
7 "io/ioutil" 6 "io/ioutil"
8 "log" 7 "log"
9 "net" 8 "net"
10 "net/http" 9 "net/http"
11 "net/url"
12 "regexp" 10 "regexp"
13 "strings" 11 "strings"
14 12
@@ -18,41 +16,58 @@ import (
18) 16)
19 17
20const ( 18const (
21 ACME_AUTH_KEY = "ACMEAuthUserID" 19 ACME_AUTH_KEY = "ACMEAuthUserID"
22 DDNS_AUTH_KEY = "DDNSAuthZone" 20 DDNS_AUTH_KEY = "DDNSAuthZone"
21 CTX_SERVER_CONFIG = "ServerConfig"
23) 22)
24 23
25var ( 24var (
26 cfg *bind.BINDConfig
27 secrets *Secrets
28 ipRegexp = regexp.MustCompile(`(?:\[([0-9a-f:]+)\]|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})):\d+`) 25 ipRegexp = regexp.MustCompile(`(?:\[([0-9a-f:]+)\]|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})):\d+`)
29) 26)
30 27
31func init() { 28type ServerConfig struct {
32 var err error 29 BindConfig *bind.BINDConfig
33 cfg, err = bind.ParseBINDConfig("zones.conf") 30 DNSClient *dns.DNSClient
31 ddnsSecrets map[string]string `json:"DDNS"`
32 acmeSecrets map[string]map[string]int `json:"ACME"`
33}
34
35func LoadServerConfig(zonesFile, secretsFile string) (*ServerConfig, error) {
36 scfg := &ServerConfig{
37 // TODO: Remove
38 DNSClient: &dns.DNSClient{Server: "172.16.18.52:53"},
39 }
40
41 cfg, err := bind.ParseBINDConfig(zonesFile)
34 if err != nil { 42 if err != nil {
35 panic(err) 43 return nil, err
36 } 44 }
45 scfg.BindConfig = cfg
37 46
38 fd, err := ioutil.ReadFile("secrets.json") 47 fd, err := ioutil.ReadFile(secretsFile)
39 if err != nil { 48 if err != nil {
40 panic(err) 49 return nil, err
41 } 50 }
42 51
43 secrets = &Secrets{} 52 if err = json.Unmarshal(fd, scfg); err != nil {
44 if err = json.Unmarshal(fd, secrets); err != nil { 53 return nil, err
45 panic(err)
46 } 54 }
55
56 return scfg, nil
57}
58
59func (s *ServerConfig) GetDDNZoneName(k string) string {
60 v, _ := s.ddnsSecrets[k]
61 return v
47} 62}
48 63
49type Secrets struct { 64func (s *ServerConfig) AcmeSecretExists(k string) bool {
50 DDNS map[string]string 65 _, ok := s.acmeSecrets[k]
51 ACME map[string]map[string]int 66 return ok
52} 67}
53 68
54func (s *Secrets) IsACMEClientAllowed(key, zone string) bool { 69func (s *ServerConfig) IsAcmeClientAllowed(key, zone string) bool {
55 u, ok := s.ACME[key] 70 u, ok := s.acmeSecrets[key]
56 if !ok { 71 if !ok {
57 return false 72 return false
58 } 73 }
@@ -74,12 +89,12 @@ type DDNSUpdateRequest struct {
74 Key string `form:"key" binding:"required"` 89 Key string `form:"key" binding:"required"`
75} 90}
76 91
77type ACMEChallenge struct { 92type AcmeChallenge struct {
78 Zone string `json:"zone" binding:"required"` 93 Zone string `json:"zone" binding:"required"`
79 Challenge string `json:"challenge" binding:"required"` 94 Challenge string `json:"challenge" binding:"required"`
80} 95}
81 96
82type ACMEChallengeID struct { 97type AcmeChallengeID struct {
83 Zone string 98 Zone string
84 Prefix string 99 Prefix string
85 Challenge string 100 Challenge string
@@ -95,49 +110,10 @@ func joinDomainParts(parts ...string) string {
95 return strings.Join(p, ".") 110 return strings.Join(p, ".")
96} 111}
97 112
98// Find the closest zone that we manage by striping dotted components off the
99// front of the domain until one matches. If there is a match return the zone
100// that matched and any prefix components, if any, as a dotted string. If none
101// match then return nil.
102func findClosestZone(cfg *bind.BINDConfig, zoneIn, view string) (*bind.Zone, string) {
103 suffix := ""
104 prefix := []string{}
105
106 zc := strings.Split(zoneIn, ".")
107 for i := 0; i <= len(zc)-2; i++ {
108 prefix, suffix = zc[:i], strings.Join(zc[i:], ".")
109 if zone := cfg.Zone(view, suffix); zone != nil {
110 return zone, strings.Join(prefix, ".")
111 }
112 }
113
114 return nil, ""
115}
116
117func makeURL(r *http.Request, path string, subs ...interface{}) *url.URL {
118 scheme := "https"
119
120 if r.TLS == nil {
121 scheme = "http"
122 }
123
124 // Always defer to whatever the proxy told us it was doing because this
125 // could be a mullet-VIP in either direction.
126 if fwProto := r.Header.Get("X-Forwarded-Proto"); fwProto != "" {
127 scheme = fwProto
128 }
129
130 return &url.URL{
131 Scheme: scheme,
132 Host: r.Host,
133 Path: fmt.Sprintf(path, subs...),
134 }
135}
136
137func createAcmeChallenge(c *gin.Context) { 113func createAcmeChallenge(c *gin.Context) {
138 dc := dns.DNSClient{Server: "172.16.18.52:53"} 114 cfg := GetServerConfig(c)
139 115
140 var ch ACMEChallenge 116 var ch AcmeChallenge
141 if err := c.ShouldBindJSON(&ch); err != nil { 117 if err := c.ShouldBindJSON(&ch); err != nil {
142 c.JSON(http.StatusBadRequest, gin.H{ 118 c.JSON(http.StatusBadRequest, gin.H{
143 "error": err.Error(), 119 "error": err.Error(),
@@ -145,7 +121,7 @@ func createAcmeChallenge(c *gin.Context) {
145 return 121 return
146 } 122 }
147 123
148 zone, prefix := findClosestZone(cfg, ch.Zone, "external") 124 zone, prefix := cfg.BindConfig.FindClosestZone(ch.Zone, "external")
149 if zone == nil { 125 if zone == nil {
150 c.JSON(http.StatusNotFound, gin.H{ 126 c.JSON(http.StatusNotFound, gin.H{
151 "error": "Zone not found", 127 "error": "Zone not found",
@@ -153,7 +129,7 @@ func createAcmeChallenge(c *gin.Context) {
153 return 129 return
154 } 130 }
155 131
156 if v := c.GetString(ACME_AUTH_KEY); !secrets.IsACMEClientAllowed(v, zone.Name) { 132 if v := c.GetString(ACME_AUTH_KEY); !cfg.IsAcmeClientAllowed(v, zone.Name) {
157 c.JSON(http.StatusForbidden, gin.H{ 133 c.JSON(http.StatusForbidden, gin.H{
158 "error": "Zone update not allowed", 134 "error": "Zone update not allowed",
159 }) 135 })
@@ -161,7 +137,7 @@ func createAcmeChallenge(c *gin.Context) {
161 } 137 }
162 138
163 // Do this first, in-case it fails (even though it should never fail) 139 // Do this first, in-case it fails (even though it should never fail)
164 id, err := json.Marshal(ACMEChallengeID{ 140 id, err := json.Marshal(AcmeChallengeID{
165 Zone: zone.Name, 141 Zone: zone.Name,
166 Prefix: prefix, 142 Prefix: prefix,
167 Challenge: ch.Challenge, 143 Challenge: ch.Challenge,
@@ -183,7 +159,7 @@ func createAcmeChallenge(c *gin.Context) {
183 } 159 }
184 160
185 // Cleanup any old challenges before adding a new one 161 // Cleanup any old challenges before adding a new one
186 if err := dc.RemoveAll(zone, t); err != nil { 162 if err := cfg.DNSClient.RemoveAll(zone, t); err != nil {
187 log.Printf("error RemoveAll: %s", err) 163 log.Printf("error RemoveAll: %s", err)
188 c.JSON(http.StatusInternalServerError, gin.H{ 164 c.JSON(http.StatusInternalServerError, gin.H{
189 "error": err.Error(), 165 "error": err.Error(),
@@ -191,7 +167,7 @@ func createAcmeChallenge(c *gin.Context) {
191 return 167 return
192 } 168 }
193 169
194 if err := dc.Insert(zone, t); err != nil { 170 if err := cfg.DNSClient.Insert(zone, t); err != nil {
195 log.Printf("error Insert: %s", err) 171 log.Printf("error Insert: %s", err)
196 c.JSON(http.StatusInternalServerError, gin.H{ 172 c.JSON(http.StatusInternalServerError, gin.H{
197 "error": err.Error(), 173 "error": err.Error(),
@@ -206,7 +182,7 @@ func createAcmeChallenge(c *gin.Context) {
206} 182}
207 183
208func deleteAcmeChallenge(c *gin.Context) { 184func deleteAcmeChallenge(c *gin.Context) {
209 dc := dns.DNSClient{Server: "172.16.18.52:53"} 185 cfg := GetServerConfig(c)
210 186
211 rid, err := base64.URLEncoding.DecodeString(c.Param("id")) 187 rid, err := base64.URLEncoding.DecodeString(c.Param("id"))
212 if err != nil { 188 if err != nil {
@@ -216,7 +192,7 @@ func deleteAcmeChallenge(c *gin.Context) {
216 return 192 return
217 } 193 }
218 194
219 var id ACMEChallengeID 195 var id AcmeChallengeID
220 if err = json.Unmarshal(rid, &id); err != nil { 196 if err = json.Unmarshal(rid, &id); err != nil {
221 c.JSON(http.StatusBadRequest, gin.H{ 197 c.JSON(http.StatusBadRequest, gin.H{
222 "error": "unable to decode ID", 198 "error": "unable to decode ID",
@@ -224,7 +200,7 @@ func deleteAcmeChallenge(c *gin.Context) {
224 return 200 return
225 } 201 }
226 202
227 zone := cfg.Zone("external", id.Zone) 203 zone := cfg.BindConfig.Zone("external", id.Zone)
228 if zone == nil { 204 if zone == nil {
229 c.JSON(http.StatusNotFound, gin.H{ 205 c.JSON(http.StatusNotFound, gin.H{
230 "error": "Zone not found", 206 "error": "Zone not found",
@@ -232,7 +208,7 @@ func deleteAcmeChallenge(c *gin.Context) {
232 return 208 return
233 } 209 }
234 210
235 if v := c.GetString(ACME_AUTH_KEY); !secrets.IsACMEClientAllowed(v, zone.Name) { 211 if v := c.GetString(ACME_AUTH_KEY); !cfg.IsAcmeClientAllowed(v, zone.Name) {
236 c.JSON(http.StatusForbidden, gin.H{ 212 c.JSON(http.StatusForbidden, gin.H{
237 "error": "Zone update not allowed", 213 "error": "Zone update not allowed",
238 }) 214 })
@@ -245,7 +221,7 @@ func deleteAcmeChallenge(c *gin.Context) {
245 Txt: []string{id.Challenge}, 221 Txt: []string{id.Challenge},
246 } 222 }
247 223
248 if err := dc.Remove(zone, t); err != nil { 224 if err := cfg.DNSClient.Remove(zone, t); err != nil {
249 log.Printf("error Remove: %s", err) 225 log.Printf("error Remove: %s", err)
250 c.JSON(http.StatusInternalServerError, gin.H{ 226 c.JSON(http.StatusInternalServerError, gin.H{
251 "error": err.Error(), 227 "error": err.Error(),
@@ -257,7 +233,7 @@ func deleteAcmeChallenge(c *gin.Context) {
257} 233}
258 234
259func updateDynamicDNS(c *gin.Context) { 235func updateDynamicDNS(c *gin.Context) {
260 dc := dns.DNSClient{Server: "172.16.18.52:53"} 236 cfg := GetServerConfig(c)
261 237
262 res := c.GetString(DDNS_AUTH_KEY) 238 res := c.GetString(DDNS_AUTH_KEY)
263 if res == "" { 239 if res == "" {
@@ -266,7 +242,7 @@ func updateDynamicDNS(c *gin.Context) {
266 return 242 return
267 } 243 }
268 244
269 zone, part := findClosestZone(cfg, res, "external") 245 zone, part := cfg.BindConfig.FindClosestZone(res, "external")
270 if zone == nil { 246 if zone == nil {
271 log.Println("ddns: Unable to locate zone") 247 log.Println("ddns: Unable to locate zone")
272 c.AbortWithStatus(http.StatusNotFound) 248 c.AbortWithStatus(http.StatusNotFound)
@@ -292,13 +268,13 @@ func updateDynamicDNS(c *gin.Context) {
292 } 268 }
293 269
294 // Cleanup any old records before adding the new one 270 // Cleanup any old records before adding the new one
295 if err := dc.RemoveAll(zone, t); err != nil { 271 if err := cfg.DNSClient.RemoveAll(zone, t); err != nil {
296 log.Printf("ddns RemoveAll: %s", err) 272 log.Printf("ddns RemoveAll: %s", err)
297 c.AbortWithStatus(http.StatusInternalServerError) 273 c.AbortWithStatus(http.StatusInternalServerError)
298 return 274 return
299 } 275 }
300 276
301 if err := dc.Insert(zone, t); err != nil { 277 if err := cfg.DNSClient.Insert(zone, t); err != nil {
302 log.Printf("ddns Insert: %s", err) 278 log.Printf("ddns Insert: %s", err)
303 c.AbortWithStatus(http.StatusInternalServerError) 279 c.AbortWithStatus(http.StatusInternalServerError)
304 return 280 return
@@ -330,15 +306,17 @@ func reflectIP(c *gin.Context) {
330 } 306 }
331} 307}
332 308
333func acmeAuth(c *gin.Context) { 309func acmeAuthMiddleware(c *gin.Context) {
310 cfg := GetServerConfig(c)
311
334 _, pwd, ok := c.Request.BasicAuth() 312 _, pwd, ok := c.Request.BasicAuth()
335 if !ok { 313 if !ok {
336 c.Request.Header["WWW-Authenticate"] = []string{`Basic realm="closed site"`} 314 c.Request.Header.Set("WWW-Authenticate", `Basic realm="closed site"`)
337 c.AbortWithStatus(http.StatusUnauthorized) 315 c.AbortWithStatus(http.StatusUnauthorized)
338 return 316 return
339 } 317 }
340 318
341 if _, ok := secrets.ACME[pwd]; !ok { 319 if !cfg.AcmeSecretExists(pwd) {
342 c.AbortWithStatus(http.StatusForbidden) 320 c.AbortWithStatus(http.StatusForbidden)
343 return 321 return
344 } else { 322 } else {
@@ -348,17 +326,19 @@ func acmeAuth(c *gin.Context) {
348 c.Next() 326 c.Next()
349} 327}
350 328
351func ddnsAuth(c *gin.Context) { 329func ddnsAuthMiddleware(c *gin.Context) {
330 cfg := GetServerConfig(c)
331
352 var req DDNSUpdateRequest 332 var req DDNSUpdateRequest
353 if err := c.ShouldBind(&req); err != nil { 333 if err := c.ShouldBind(&req); err != nil {
354 log.Println("ddnsAuth: No key in request") 334 log.Println("ddnsAuthMiddleware: No key in request")
355 c.AbortWithStatus(http.StatusNotFound) 335 c.AbortWithStatus(http.StatusNotFound)
356 return 336 return
357 } 337 }
358 338
359 res, ok := secrets.DDNS[req.Key] 339 res := cfg.GetDDNZoneName(req.Key)
360 if !ok { 340 if res == "" {
361 log.Println("ddnsAuth: Unknown secret") 341 log.Println("ddnsAuthMiddleware: Unknown secret")
362 c.AbortWithStatus(http.StatusNotFound) 342 c.AbortWithStatus(http.StatusNotFound)
363 return 343 return
364 } else { 344 } else {
@@ -368,21 +348,44 @@ func ddnsAuth(c *gin.Context) {
368 c.Next() 348 c.Next()
369} 349}
370 350
351func ConfigContextMiddleware(cfg *ServerConfig) func(*gin.Context) {
352 return func(c *gin.Context) {
353 c.Set(CTX_SERVER_CONFIG, cfg)
354 c.Next()
355 }
356}
357
358func GetServerConfig(c *gin.Context) *ServerConfig {
359 v, ok := c.Get(CTX_SERVER_CONFIG)
360 if !ok {
361 // This should never happen if the config context middlware is in place
362 panic("Unable to get config from request")
363 }
364
365 return v.(*ServerConfig)
366}
367
371func main() { 368func main() {
372 gin.SetMode(gin.DebugMode) 369 gin.SetMode(gin.DebugMode)
373 370
371 cfg, err := LoadServerConfig("cfg/zones.conf", "cfg/secrets.json")
372 if err != nil {
373 panic(err)
374 }
375
374 router := gin.Default() 376 router := gin.Default()
377 router.Use(ConfigContextMiddleware(cfg))
375 378
376 router.GET("/reflect-ip", reflectIP) 379 router.GET("/reflect-ip", reflectIP)
377 380
378 ddns := router.Group("/dynamic-dns") 381 ddns := router.Group("/dynamic-dns")
379 ddns.Use(ddnsAuth) 382 ddns.Use(ddnsAuthMiddleware)
380 { 383 {
381 ddns.POST("", updateDynamicDNS) 384 ddns.POST("", updateDynamicDNS)
382 } 385 }
383 386
384 acme := router.Group("/acme") 387 acme := router.Group("/acme")
385 acme.Use(acmeAuth) 388 acme.Use(acmeAuthMiddleware)
386 { 389 {
387 acme.POST("", createAcmeChallenge) 390 acme.POST("", createAcmeChallenge)
388 acme.DELETE("/:id", deleteAcmeChallenge) 391 acme.DELETE("/:id", deleteAcmeChallenge)
diff --git a/web.go b/web.go
new file mode 100644
index 0000000..b64c1de
--- /dev/null
+++ b/web.go
@@ -0,0 +1,26 @@
1package main
2
3import (
4 "fmt"
5 "net/http"
6 "net/url"
7)
8
9func makeURL(r *http.Request, path string, subs ...interface{}) *url.URL {
10 scheme := "https"
11 if r.TLS == nil {
12 scheme = "http"
13 }
14
15 // Always defer to whatever the proxy told us it was doing because this
16 // could be a mullet-VIP in either direction.
17 if fwProto := r.Header.Get("X-Forwarded-Proto"); fwProto != "" {
18 scheme = fwProto
19 }
20
21 return &url.URL{
22 Scheme: scheme,
23 Host: r.Host,
24 Path: fmt.Sprintf(path, subs...),
25 }
26}